blob: 8d4e1eebbcd76f4f1a39533efbebb4db2a453cf3 [file] [log] [blame]
/*
* Copyright (C) 2012 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.bluetooth.hfp;
import android.content.Context;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.util.Log;
import android.bluetooth.BluetoothDevice;
// Note:
// All methods in this class are not thread safe, donot call them from
// multiple threads. Call them from the HeadsetPhoneStateMachine message
// handler only.
class HeadsetPhoneState {
private static final String TAG = "HeadsetPhoneState";
private HeadsetStateMachine mStateMachine;
private TelephonyManager mTelephonyManager;
private ServiceState mServiceState;
// HFP 1.6 CIND service
private int mService = HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
// Number of active (foreground) calls
private int mNumActive = 0;
// Current Call Setup State
private int mCallState = HeadsetHalConstants.CALL_STATE_IDLE;
// Number of held (background) calls
private int mNumHeld = 0;
// HFP 1.6 CIND signal
private int mSignal = 0;
// HFP 1.6 CIND roam
private int mRoam = HeadsetHalConstants.SERVICE_TYPE_HOME;
// HFP 1.6 CIND battchg
private int mBatteryCharge = 0;
private int mSpeakerVolume = 0;
private int mMicVolume = 0;
private boolean mListening = false;
// when HFP Service Level Connection is established
private boolean mSlcReady = false;
private Context mContext = null;
private PhoneStateListener mPhoneStateListener = null;
private SubscriptionManager mSubMgr;
private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
new OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
listenForPhoneState(false);
listenForPhoneState(true);
}
};
HeadsetPhoneState(Context context, HeadsetStateMachine stateMachine) {
mStateMachine = stateMachine;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mContext = context;
// Register for SubscriptionInfo list changes which is guaranteed
// to invoke onSubscriptionInfoChanged and which in turns calls
// loadInBackgroud.
mSubMgr = SubscriptionManager.from(mContext);
mSubMgr.registerOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
}
public void cleanup() {
listenForPhoneState(false);
mSubMgr.unregisterOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
mTelephonyManager = null;
mStateMachine = null;
}
void listenForPhoneState(boolean start) {
mSlcReady = start;
if (start) {
startListenForPhoneState();
} else {
stopListenForPhoneState();
}
}
private void startListenForPhoneState() {
if (!mListening && mSlcReady) {
int subId = SubscriptionManager.getDefaultSubId();
if (SubscriptionManager.isValidSubscriptionId(subId)) {
mPhoneStateListener = getPhoneStateListener(subId);
mTelephonyManager.listen(mPhoneStateListener,
PhoneStateListener.LISTEN_SERVICE_STATE |
PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
mListening = true;
}
}
}
private void stopListenForPhoneState() {
if (mListening) {
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
mListening = false;
}
}
int getService() {
return mService;
}
int getNumActiveCall() {
return mNumActive;
}
void setNumActiveCall(int numActive) {
mNumActive = numActive;
}
int getCallState() {
return mCallState;
}
void setCallState(int callState) {
mCallState = callState;
}
int getNumHeldCall() {
return mNumHeld;
}
void setNumHeldCall(int numHeldCall) {
mNumHeld = numHeldCall;
}
int getSignal() {
return mSignal;
}
int getRoam() {
return mRoam;
}
void setRoam(int roam) {
mRoam = roam;
}
void setBatteryCharge(int batteryLevel) {
if (mBatteryCharge != batteryLevel) {
mBatteryCharge = batteryLevel;
sendDeviceStateChanged();
}
}
int getBatteryCharge() {
return mBatteryCharge;
}
void setSpeakerVolume(int volume) {
mSpeakerVolume = volume;
}
int getSpeakerVolume() {
return mSpeakerVolume;
}
void setMicVolume(int volume) {
mMicVolume = volume;
}
int getMicVolume() {
return mMicVolume;
}
boolean isInCall() {
return (mNumActive >= 1);
}
void sendDeviceStateChanged()
{
// When out of service, send signal strength as 0. Some devices don't
// use the service indicator, but only the signal indicator
int signal = mService == HeadsetHalConstants.NETWORK_STATE_AVAILABLE ? mSignal : 0;
Log.d(TAG, "sendDeviceStateChanged. mService="+ mService +
" mSignal=" + signal +" mRoam="+ mRoam +
" mBatteryCharge=" + mBatteryCharge);
HeadsetStateMachine sm = mStateMachine;
if (sm != null) {
sm.sendMessage(HeadsetStateMachine.DEVICE_STATE_CHANGED,
new HeadsetDeviceState(mService, mRoam, signal, mBatteryCharge));
}
}
private PhoneStateListener getPhoneStateListener(int subId) {
PhoneStateListener mPhoneStateListener = new PhoneStateListener(subId) {
@Override
public void onServiceStateChanged(ServiceState serviceState) {
mServiceState = serviceState;
mService = (serviceState.getState() == ServiceState.STATE_IN_SERVICE) ?
HeadsetHalConstants.NETWORK_STATE_AVAILABLE :
HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
setRoam(serviceState.getRoaming() ? HeadsetHalConstants.SERVICE_TYPE_ROAMING
: HeadsetHalConstants.SERVICE_TYPE_HOME);
sendDeviceStateChanged();
}
@Override
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
int prevSignal = mSignal;
if (mService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
mSignal = 0;
} else if (signalStrength.isGsm()) {
mSignal = gsmAsuToSignal(signalStrength);
} else {
mSignal = cdmaDbmEcioToSignal(signalStrength);
}
// network signal strength is scaled to BT 1-5 levels.
// This results in a lot of duplicate messages, hence this check
if (prevSignal != mSignal) {
sendDeviceStateChanged();
}
}
/* convert [0,31] ASU signal strength to the [0,5] expected by
* bluetooth devices. Scale is similar to status bar policy
*/
private int gsmAsuToSignal(SignalStrength signalStrength) {
int asu = signalStrength.getGsmSignalStrength();
if (asu >= 16) return 5;
else if (asu >= 8) return 4;
else if (asu >= 4) return 3;
else if (asu >= 2) return 2;
else if (asu >= 1) return 1;
else return 0;
}
/**
* Convert the cdma / evdo db levels to appropriate icon level.
* The scale is similar to the one used in status bar policy.
*
* @param signalStrength
* @return the icon level
*/
private int cdmaDbmEcioToSignal(SignalStrength signalStrength) {
int levelDbm = 0;
int levelEcio = 0;
int cdmaIconLevel = 0;
int evdoIconLevel = 0;
int cdmaDbm = signalStrength.getCdmaDbm();
int cdmaEcio = signalStrength.getCdmaEcio();
if (cdmaDbm >= -75) levelDbm = 4;
else if (cdmaDbm >= -85) levelDbm = 3;
else if (cdmaDbm >= -95) levelDbm = 2;
else if (cdmaDbm >= -100) levelDbm = 1;
else levelDbm = 0;
// Ec/Io are in dB*10
if (cdmaEcio >= -90) levelEcio = 4;
else if (cdmaEcio >= -110) levelEcio = 3;
else if (cdmaEcio >= -130) levelEcio = 2;
else if (cdmaEcio >= -150) levelEcio = 1;
else levelEcio = 0;
cdmaIconLevel = (levelDbm < levelEcio) ? levelDbm : levelEcio;
// STOPSHIP: Change back to getRilVoiceRadioTechnology
if (mServiceState != null &&
(mServiceState.getRadioTechnology() ==
ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0 ||
mServiceState.getRadioTechnology() ==
ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A)) {
int evdoEcio = signalStrength.getEvdoEcio();
int evdoSnr = signalStrength.getEvdoSnr();
int levelEvdoEcio = 0;
int levelEvdoSnr = 0;
// Ec/Io are in dB*10
if (evdoEcio >= -650) levelEvdoEcio = 4;
else if (evdoEcio >= -750) levelEvdoEcio = 3;
else if (evdoEcio >= -900) levelEvdoEcio = 2;
else if (evdoEcio >= -1050) levelEvdoEcio = 1;
else levelEvdoEcio = 0;
if (evdoSnr > 7) levelEvdoSnr = 4;
else if (evdoSnr > 5) levelEvdoSnr = 3;
else if (evdoSnr > 3) levelEvdoSnr = 2;
else if (evdoSnr > 1) levelEvdoSnr = 1;
else levelEvdoSnr = 0;
evdoIconLevel = (levelEvdoEcio < levelEvdoSnr) ? levelEvdoEcio : levelEvdoSnr;
}
// TODO(): There is a bug open regarding what should be sent.
return (cdmaIconLevel > evdoIconLevel) ? cdmaIconLevel : evdoIconLevel;
}
};
return mPhoneStateListener;
}
}
class HeadsetDeviceState {
int mService;
int mRoam;
int mSignal;
int mBatteryCharge;
HeadsetDeviceState(int service, int roam, int signal, int batteryCharge) {
mService = service;
mRoam = roam;
mSignal = signal;
mBatteryCharge = batteryCharge;
}
}
class HeadsetCallState {
int mNumActive;
int mNumHeld;
int mCallState;
String mNumber;
int mType;
public HeadsetCallState(int numActive, int numHeld, int callState, String number, int type) {
mNumActive = numActive;
mNumHeld = numHeld;
mCallState = callState;
mNumber = number;
mType = type;
}
}
class HeadsetClccResponse {
int mIndex;
int mDirection;
int mStatus;
int mMode;
boolean mMpty;
String mNumber;
int mType;
public HeadsetClccResponse(int index, int direction, int status, int mode, boolean mpty,
String number, int type) {
mIndex = index;
mDirection = direction;
mStatus = status;
mMode = mode;
mMpty = mpty;
mNumber = number;
mType = type;
}
}
class HeadsetVendorSpecificResultCode {
BluetoothDevice mDevice;
String mCommand;
String mArg;
public HeadsetVendorSpecificResultCode(BluetoothDevice device, String command, String arg) {
mDevice = device;
mCommand = command;
mArg = arg;
}
}