494 lines
17 KiB
Java
494 lines
17 KiB
Java
|
/*
|
||
|
* 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 android.telephony.TelephonyManager.CALL_STATE_IDLE;
|
||
|
import static android.telephony.TelephonyManager.CALL_STATE_OFFHOOK;
|
||
|
import static android.telephony.TelephonyManager.CALL_STATE_RINGING;
|
||
|
|
||
|
import android.content.BroadcastReceiver;
|
||
|
import android.content.Context;
|
||
|
import android.content.Intent;
|
||
|
import android.content.IntentFilter;
|
||
|
import android.media.AudioAttributes;
|
||
|
import android.media.AudioDeviceAttributes;
|
||
|
import android.media.AudioDeviceInfo;
|
||
|
import android.media.AudioManager;
|
||
|
import android.net.wifi.WifiManager;
|
||
|
import android.os.Handler;
|
||
|
import android.os.Looper;
|
||
|
import android.os.PowerManager;
|
||
|
import android.telephony.PhoneStateListener;
|
||
|
import android.telephony.TelephonyManager;
|
||
|
import android.text.TextUtils;
|
||
|
import android.util.Log;
|
||
|
|
||
|
import com.android.modules.utils.HandlerExecutor;
|
||
|
import com.android.wifi.resources.R;
|
||
|
|
||
|
import java.io.FileDescriptor;
|
||
|
import java.io.PrintWriter;
|
||
|
import java.util.List;
|
||
|
|
||
|
/**
|
||
|
* This class provides the Support for SAR to control WiFi TX power limits.
|
||
|
* It deals with the following:
|
||
|
* - Tracking the STA state through calls from the ClientModeManager.
|
||
|
* - Tracking the SAP state through calls from SoftApManager
|
||
|
* - Tracking the Scan-Only state through ScanOnlyModeManager
|
||
|
* - Tracking the state of the Cellular calls or data.
|
||
|
* - It constructs the sar info and send it towards the HAL
|
||
|
*/
|
||
|
public class SarManager {
|
||
|
// Period for checking on voice steam active (in ms)
|
||
|
private static final int CHECK_VOICE_STREAM_INTERVAL_MS = 5000;
|
||
|
|
||
|
/**
|
||
|
* @hide constants copied over from {@link AudioManager}
|
||
|
* TODO(b/144250387): Migrate to public API
|
||
|
*/
|
||
|
private static final String STREAM_DEVICES_CHANGED_ACTION =
|
||
|
"android.media.stream_devices_changed_action";
|
||
|
private static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE";
|
||
|
private static final String EXTRA_VOLUME_STREAM_DEVICES =
|
||
|
"android.media.EXTRA_VOLUME_STREAM_DEVICES";
|
||
|
private static final String EXTRA_PREV_VOLUME_STREAM_DEVICES =
|
||
|
"android.media.EXTRA_PREV_VOLUME_STREAM_DEVICES";
|
||
|
private static final int DEVICE_OUT_EARPIECE = 0x1;
|
||
|
|
||
|
/* For Logging */
|
||
|
private static final String TAG = "WifiSarManager";
|
||
|
private boolean mVerboseLoggingEnabled = true;
|
||
|
|
||
|
private SarInfo mSarInfo;
|
||
|
|
||
|
/* Configuration for SAR support */
|
||
|
private boolean mSupportSarTxPowerLimit;
|
||
|
private boolean mSupportSarVoiceCall;
|
||
|
private boolean mSupportSarSoftAp;
|
||
|
|
||
|
// Device starts with screen on
|
||
|
private boolean mScreenOn = false;
|
||
|
private boolean mIsVoiceStreamCheckEnabled = false;
|
||
|
|
||
|
/**
|
||
|
* Other parameters passed in or created in the constructor.
|
||
|
*/
|
||
|
private final Context mContext;
|
||
|
private final TelephonyManager mTelephonyManager;
|
||
|
private final AudioManager mAudioManager;
|
||
|
private final WifiPhoneStateListener mPhoneStateListener;
|
||
|
private final WifiNative mWifiNative;
|
||
|
private final Handler mHandler;
|
||
|
|
||
|
/**
|
||
|
* Create new instance of SarManager.
|
||
|
*/
|
||
|
SarManager(Context context,
|
||
|
TelephonyManager telephonyManager,
|
||
|
Looper looper,
|
||
|
WifiNative wifiNative) {
|
||
|
mContext = context;
|
||
|
mTelephonyManager = telephonyManager;
|
||
|
mWifiNative = wifiNative;
|
||
|
mAudioManager = mContext.getSystemService(AudioManager.class);
|
||
|
mHandler = new Handler(looper);
|
||
|
mPhoneStateListener = new WifiPhoneStateListener(looper);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Handle boot completed, read config flags.
|
||
|
*/
|
||
|
public void handleBootCompleted() {
|
||
|
readSarConfigs();
|
||
|
if (mSupportSarTxPowerLimit) {
|
||
|
mSarInfo = new SarInfo();
|
||
|
setSarConfigsInInfo();
|
||
|
registerListeners();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Notify SarManager of screen status change
|
||
|
*/
|
||
|
private void handleScreenStateChanged(boolean screenOn) {
|
||
|
if (!mSupportSarVoiceCall) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (mScreenOn == screenOn) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (mVerboseLoggingEnabled) {
|
||
|
Log.d(TAG, "handleScreenStateChanged: screenOn = " + screenOn);
|
||
|
}
|
||
|
|
||
|
mScreenOn = screenOn;
|
||
|
|
||
|
// Only schedule a voice stream check if screen is turning on, and it is currently not
|
||
|
// scheduled
|
||
|
if (mScreenOn && !mIsVoiceStreamCheckEnabled) {
|
||
|
mHandler.post(() -> {
|
||
|
checkAudioDevice();
|
||
|
});
|
||
|
|
||
|
mIsVoiceStreamCheckEnabled = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private boolean isVoiceCallOnEarpiece() {
|
||
|
final AudioAttributes voiceCallAttr = new AudioAttributes.Builder()
|
||
|
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
|
||
|
.build();
|
||
|
List<AudioDeviceAttributes> devices = mAudioManager.getDevicesForAttributes(voiceCallAttr);
|
||
|
for (AudioDeviceAttributes device : devices) {
|
||
|
if (device.getRole() == AudioDeviceAttributes.ROLE_OUTPUT
|
||
|
&& device.getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private boolean isVoiceCallStreamActive() {
|
||
|
int mode = mAudioManager.getMode();
|
||
|
return mode == AudioManager.MODE_IN_COMMUNICATION || mode == AudioManager.MODE_IN_CALL;
|
||
|
}
|
||
|
|
||
|
private void checkAudioDevice() {
|
||
|
// First Check if audio stream is on
|
||
|
boolean voiceStreamActive = isVoiceCallStreamActive();
|
||
|
boolean earPieceActive;
|
||
|
|
||
|
if (voiceStreamActive) {
|
||
|
// Check on the audio route
|
||
|
earPieceActive = isVoiceCallOnEarpiece();
|
||
|
|
||
|
if (mVerboseLoggingEnabled) {
|
||
|
Log.d(TAG, "EarPiece active = " + earPieceActive);
|
||
|
}
|
||
|
} else {
|
||
|
earPieceActive = false;
|
||
|
}
|
||
|
|
||
|
// If audio route has changed, update SAR
|
||
|
if (earPieceActive != mSarInfo.isEarPieceActive) {
|
||
|
mSarInfo.isEarPieceActive = earPieceActive;
|
||
|
updateSarScenario();
|
||
|
}
|
||
|
|
||
|
// Now should we proceed with the checks
|
||
|
if (!mScreenOn && !voiceStreamActive) {
|
||
|
// No need to continue checking
|
||
|
mIsVoiceStreamCheckEnabled = false;
|
||
|
} else {
|
||
|
// Schedule another check
|
||
|
mHandler.postDelayed(() -> {
|
||
|
checkAudioDevice();
|
||
|
}, CHECK_VOICE_STREAM_INTERVAL_MS);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void readSarConfigs() {
|
||
|
mSupportSarTxPowerLimit = mContext.getResources().getBoolean(
|
||
|
R.bool.config_wifi_framework_enable_sar_tx_power_limit);
|
||
|
/* In case SAR is disabled,
|
||
|
then all SAR inputs are automatically disabled as well (irrespective of the config) */
|
||
|
if (!mSupportSarTxPowerLimit) {
|
||
|
mSupportSarVoiceCall = false;
|
||
|
mSupportSarSoftAp = false;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Voice calls are supported when SAR is supported */
|
||
|
mSupportSarVoiceCall = true;
|
||
|
|
||
|
mSupportSarSoftAp = mContext.getResources().getBoolean(
|
||
|
R.bool.config_wifi_framework_enable_soft_ap_sar_tx_power_limit);
|
||
|
}
|
||
|
|
||
|
private void setSarConfigsInInfo() {
|
||
|
mSarInfo.sarVoiceCallSupported = mSupportSarVoiceCall;
|
||
|
mSarInfo.sarSapSupported = mSupportSarSoftAp;
|
||
|
}
|
||
|
|
||
|
private void registerListeners() {
|
||
|
if (mSupportSarVoiceCall) {
|
||
|
/* Listen for Phone State changes */
|
||
|
registerPhoneStateListener();
|
||
|
registerVoiceStreamListener();
|
||
|
registerScreenListener();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void registerScreenListener() {
|
||
|
// Listen to screen changes
|
||
|
IntentFilter filter = new IntentFilter();
|
||
|
filter.addAction(Intent.ACTION_SCREEN_ON);
|
||
|
filter.addAction(Intent.ACTION_SCREEN_OFF);
|
||
|
|
||
|
mContext.registerReceiver(
|
||
|
new BroadcastReceiver() {
|
||
|
@Override
|
||
|
public void onReceive(Context context, Intent intent) {
|
||
|
String action = intent.getAction();
|
||
|
if (TextUtils.equals(action, Intent.ACTION_SCREEN_ON)) {
|
||
|
handleScreenStateChanged(true);
|
||
|
} else if (TextUtils.equals(action, Intent.ACTION_SCREEN_OFF)) {
|
||
|
handleScreenStateChanged(false);
|
||
|
}
|
||
|
}
|
||
|
}, filter, null, mHandler);
|
||
|
PowerManager powerManager = mContext.getSystemService(PowerManager.class);
|
||
|
handleScreenStateChanged(powerManager.isInteractive());
|
||
|
}
|
||
|
|
||
|
private void registerVoiceStreamListener() {
|
||
|
Log.i(TAG, "Registering for voice stream status");
|
||
|
|
||
|
// Register for listening to transitions of change of voice stream devices
|
||
|
IntentFilter filter = new IntentFilter();
|
||
|
filter.addAction(STREAM_DEVICES_CHANGED_ACTION);
|
||
|
|
||
|
mContext.registerReceiver(
|
||
|
new BroadcastReceiver() {
|
||
|
@Override
|
||
|
public void onReceive(Context context, Intent intent) {
|
||
|
boolean voiceStreamActive = isVoiceCallStreamActive();
|
||
|
if (!voiceStreamActive) {
|
||
|
// No need to proceed, there is no voice call ongoing
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
String action = intent.getAction();
|
||
|
int streamType =
|
||
|
intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1);
|
||
|
int device = intent.getIntExtra(EXTRA_VOLUME_STREAM_DEVICES, -1);
|
||
|
int oldDevice = intent.getIntExtra(EXTRA_PREV_VOLUME_STREAM_DEVICES, -1);
|
||
|
|
||
|
if (streamType == AudioManager.STREAM_VOICE_CALL) {
|
||
|
boolean earPieceActive = mSarInfo.isEarPieceActive;
|
||
|
if (device == DEVICE_OUT_EARPIECE) {
|
||
|
if (mVerboseLoggingEnabled) {
|
||
|
Log.d(TAG, "Switching to earpiece : HEAD ON");
|
||
|
Log.d(TAG, "Old device = " + oldDevice);
|
||
|
}
|
||
|
earPieceActive = true;
|
||
|
} else if (oldDevice == DEVICE_OUT_EARPIECE) {
|
||
|
if (mVerboseLoggingEnabled) {
|
||
|
Log.d(TAG, "Switching from earpiece : HEAD OFF");
|
||
|
Log.d(TAG, "New device = " + device);
|
||
|
}
|
||
|
earPieceActive = false;
|
||
|
}
|
||
|
|
||
|
if (earPieceActive != mSarInfo.isEarPieceActive) {
|
||
|
mSarInfo.isEarPieceActive = earPieceActive;
|
||
|
updateSarScenario();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}, filter, null, mHandler);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register the phone state listener.
|
||
|
*/
|
||
|
private void registerPhoneStateListener() {
|
||
|
Log.i(TAG, "Registering for telephony call state changes");
|
||
|
mTelephonyManager.listen(
|
||
|
mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update Wifi Client State
|
||
|
*/
|
||
|
public void setClientWifiState(int state) {
|
||
|
boolean newIsEnabled;
|
||
|
/* No action is taken if SAR is not supported */
|
||
|
if (!mSupportSarTxPowerLimit) {
|
||
|
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.isWifiClientEnabled != newIsEnabled) {
|
||
|
mSarInfo.isWifiClientEnabled = newIsEnabled;
|
||
|
updateSarScenario();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update Wifi SoftAP State
|
||
|
*/
|
||
|
public void setSapWifiState(int state) {
|
||
|
boolean newIsEnabled;
|
||
|
/* No action is taken if SAR is not supported */
|
||
|
if (!mSupportSarTxPowerLimit) {
|
||
|
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.isWifiSapEnabled != newIsEnabled) {
|
||
|
mSarInfo.isWifiSapEnabled = newIsEnabled;
|
||
|
updateSarScenario();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update Wifi ScanOnly State
|
||
|
*/
|
||
|
public void setScanOnlyWifiState(int state) {
|
||
|
boolean newIsEnabled;
|
||
|
/* No action is taken if SAR is not supported */
|
||
|
if (!mSupportSarTxPowerLimit) {
|
||
|
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.isWifiScanOnlyEnabled != newIsEnabled) {
|
||
|
mSarInfo.isWifiScanOnlyEnabled = newIsEnabled;
|
||
|
updateSarScenario();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Report Cell state event
|
||
|
*/
|
||
|
private void onCellStateChangeEvent(int state) {
|
||
|
boolean newIsVoiceCall;
|
||
|
switch (state) {
|
||
|
case CALL_STATE_OFFHOOK:
|
||
|
case CALL_STATE_RINGING:
|
||
|
newIsVoiceCall = true;
|
||
|
break;
|
||
|
|
||
|
case CALL_STATE_IDLE:
|
||
|
newIsVoiceCall = false;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
Log.e(TAG, "Invalid Cell State: " + state);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Report change to HAL if needed */
|
||
|
if (mSarInfo.isVoiceCall != newIsVoiceCall) {
|
||
|
mSarInfo.isVoiceCall = newIsVoiceCall;
|
||
|
|
||
|
if (mVerboseLoggingEnabled) {
|
||
|
Log.d(TAG, "Voice Call = " + newIsVoiceCall);
|
||
|
}
|
||
|
updateSarScenario();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Enable/disable verbose logging.
|
||
|
*/
|
||
|
public void enableVerboseLogging(boolean verboseEnabled) {
|
||
|
mVerboseLoggingEnabled = verboseEnabled;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* dump()
|
||
|
* Dumps SarManager state (as well as its SarInfo member variable state)
|
||
|
*/
|
||
|
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||
|
pw.println("Dump of SarManager");
|
||
|
pw.println("isSarSupported: " + mSupportSarTxPowerLimit);
|
||
|
pw.println("isSarVoiceCallSupported: " + mSupportSarVoiceCall);
|
||
|
pw.println("isSarSoftApSupported: " + mSupportSarSoftAp);
|
||
|
pw.println("");
|
||
|
if (mSarInfo != null) {
|
||
|
mSarInfo.dump(fd, pw, args);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Listen for phone call state events to set/reset TX power limits for SAR requirements.
|
||
|
*/
|
||
|
private class WifiPhoneStateListener extends PhoneStateListener {
|
||
|
WifiPhoneStateListener(Looper looper) {
|
||
|
super(new HandlerExecutor(new Handler(looper)));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* onCallStateChanged()
|
||
|
* This callback is called when a call state event is received
|
||
|
* Note that this runs in the WifiCoreHandlerThread
|
||
|
* 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 (!mSupportSarTxPowerLimit || !mSupportSarVoiceCall) {
|
||
|
return;
|
||
|
}
|
||
|
onCellStateChangeEvent(state);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* updateSarScenario()
|
||
|
* Update HAL with the new SAR scenario if needed.
|
||
|
*/
|
||
|
private void updateSarScenario() {
|
||
|
if (!mSarInfo.shouldReport()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Report info to HAL*/
|
||
|
if (mWifiNative.selectTxPowerScenario(mSarInfo)) {
|
||
|
mSarInfo.reportingSuccessful();
|
||
|
} else {
|
||
|
Log.e(TAG, "Failed in WifiNative.selectTxPowerScenario()");
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
}
|