blob: e2f48fdd3d4d65d070899bb4680466eb2f3c499e [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.phone;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothHeadsetPhone;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemProperties;
import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
import android.util.Log;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.CallManager;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
/**
* Bluetooth headset manager for the Phone app.
* @hide
*/
public class BluetoothPhoneService extends Service {
private static final String TAG = "BluetoothPhoneService";
private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1)
&& (SystemProperties.getInt("ro.debuggable", 0) == 1);
private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2); // even more logging
private static final String MODIFY_PHONE_STATE = android.Manifest.permission.MODIFY_PHONE_STATE;
private BluetoothAdapter mAdapter;
private CallManager mCM;
private BluetoothHeadset mBluetoothHeadset;
private PowerManager mPowerManager;
private WakeLock mStartCallWakeLock; // held while waiting for the intent to start call
private PhoneConstants.State mPhoneState = PhoneConstants.State.IDLE;
CdmaPhoneCallState.PhoneCallState mCdmaThreeWayCallState =
CdmaPhoneCallState.PhoneCallState.IDLE;
private Call.State mForegroundCallState;
private Call.State mRingingCallState;
private CallNumber mRingNumber;
// number of active calls
int mNumActive;
// number of background (held) calls
int mNumHeld;
long mBgndEarliestConnectionTime = 0;
private boolean mRoam = false;
// CDMA specific flag used in context with BT devices having display capabilities
// to show which Caller is active. This state might not be always true as in CDMA
// networks if a caller drops off no update is provided to the Phone.
// This flag is just used as a toggle to provide a update to the BT device to specify
// which caller is active.
private boolean mCdmaIsSecondCallActive = false;
private boolean mCdmaCallsSwapped = false;
private long[] mClccTimestamps; // Timestamps associated with each clcc index
private boolean[] mClccUsed; // Is this clcc index in use
private static final int GSM_MAX_CONNECTIONS = 6; // Max connections allowed by GSM
private static final int CDMA_MAX_CONNECTIONS = 2; // Max connections allowed by CDMA
@Override
public void onCreate() {
super.onCreate();
mCM = CallManager.getInstance();
mAdapter = BluetoothAdapter.getDefaultAdapter();
if (mAdapter == null) {
if (VDBG) Log.d(TAG, "mAdapter null");
return;
}
mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
mStartCallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
TAG + ":StartCall");
mStartCallWakeLock.setReferenceCounted(false);
mAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET);
mForegroundCallState = Call.State.IDLE;
mRingingCallState = Call.State.IDLE;
mNumActive = 0;
mNumHeld = 0;
mRingNumber = new CallNumber("", 0);;
mRoam = false;
updateServiceState(mCM.getDefaultPhone().getServiceState());
handlePreciseCallStateChange(null);
if(VDBG) Log.d(TAG, "registerForServiceStateChanged");
// register for updates
// Use the service state of default phone as BT service state to
// avoid situation such as no cell or wifi connection but still
// reporting in service (since SipPhone always reports in service).
mCM.getDefaultPhone().registerForServiceStateChanged(mHandler,
SERVICE_STATE_CHANGED, null);
mCM.registerForPreciseCallStateChanged(mHandler,
PRECISE_CALL_STATE_CHANGED, null);
mCM.registerForCallWaiting(mHandler,
PHONE_CDMA_CALL_WAITING, null);
// TODO(BT) registerForIncomingRing?
// TODO(BT) registerdisconnection?
mClccTimestamps = new long[GSM_MAX_CONNECTIONS];
mClccUsed = new boolean[GSM_MAX_CONNECTIONS];
for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
mClccUsed[i] = false;
}
}
@Override
public void onStart(Intent intent, int startId) {
if (mAdapter == null) {
Log.w(TAG, "Stopping Bluetooth BluetoothPhoneService Service: device does not have BT");
stopSelf();
}
if (VDBG) Log.d(TAG, "BluetoothPhoneService started");
}
@Override
public void onDestroy() {
super.onDestroy();
if (DBG) log("Stopping Bluetooth BluetoothPhoneService Service");
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private static final int SERVICE_STATE_CHANGED = 1;
private static final int PRECISE_CALL_STATE_CHANGED = 2;
private static final int PHONE_CDMA_CALL_WAITING = 3;
private static final int LIST_CURRENT_CALLS = 4;
private static final int QUERY_PHONE_STATE = 5;
private static final int CDMA_SWAP_SECOND_CALL_STATE = 6;
private static final int CDMA_SET_SECOND_CALL_STATE = 7;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (VDBG) Log.d(TAG, "handleMessage: " + msg.what);
switch(msg.what) {
case SERVICE_STATE_CHANGED:
ServiceState state = (ServiceState) ((AsyncResult) msg.obj).result;
updateServiceState(state);
break;
case PRECISE_CALL_STATE_CHANGED:
case PHONE_CDMA_CALL_WAITING:
Connection connection = null;
if (((AsyncResult) msg.obj).result instanceof Connection) {
connection = (Connection) ((AsyncResult) msg.obj).result;
}
handlePreciseCallStateChange(connection);
break;
case LIST_CURRENT_CALLS:
handleListCurrentCalls();
break;
case QUERY_PHONE_STATE:
handleQueryPhoneState();
break;
case CDMA_SWAP_SECOND_CALL_STATE:
handleCdmaSwapSecondCallState();
break;
case CDMA_SET_SECOND_CALL_STATE:
handleCdmaSetSecondCallState((Boolean) msg.obj);
break;
}
}
};
private void updateBtPhoneStateAfterRadioTechnologyChange() {
if(VDBG) Log.d(TAG, "updateBtPhoneStateAfterRadioTechnologyChange...");
//Unregister all events from the old obsolete phone
mCM.getDefaultPhone().unregisterForServiceStateChanged(mHandler);
mCM.unregisterForPreciseCallStateChanged(mHandler);
mCM.unregisterForCallWaiting(mHandler);
//Register all events new to the new active phone
mCM.getDefaultPhone().registerForServiceStateChanged(mHandler,
SERVICE_STATE_CHANGED, null);
mCM.registerForPreciseCallStateChanged(mHandler,
PRECISE_CALL_STATE_CHANGED, null);
mCM.registerForCallWaiting(mHandler,
PHONE_CDMA_CALL_WAITING, null);
}
private void updateServiceState(ServiceState state) {
boolean roam = state.getRoaming();
if (roam != mRoam) {
mRoam = roam;
if (mBluetoothHeadset != null) {
mBluetoothHeadset.roamChanged(roam);
}
}
}
private void handlePreciseCallStateChange(Connection connection) {
// get foreground call state
int oldNumActive = mNumActive;
int oldNumHeld = mNumHeld;
Call.State oldRingingCallState = mRingingCallState;
Call.State oldForegroundCallState = mForegroundCallState;
CallNumber oldRingNumber = mRingNumber;
Call foregroundCall = mCM.getActiveFgCall();
if (VDBG)
Log.d(TAG, " handlePreciseCallStateChange: foreground: " + foregroundCall +
" background: " + mCM.getFirstActiveBgCall() + " ringing: " +
mCM.getFirstActiveRingingCall());
mForegroundCallState = foregroundCall.getState();
/* if in transition, do not update */
if (mForegroundCallState == Call.State.DISCONNECTING)
{
Log.d(TAG, "handlePreciseCallStateChange. Call disconnecting, wait before update");
return;
}
else
mNumActive = (mForegroundCallState == Call.State.ACTIVE) ? 1 : 0;
Call ringingCall = mCM.getFirstActiveRingingCall();
mRingingCallState = ringingCall.getState();
mRingNumber = getCallNumber(connection, ringingCall);
if (mCM.getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
mNumHeld = getNumHeldCdma();
PhoneGlobals app = PhoneGlobals.getInstance();
if (app.cdmaPhoneCallState != null) {
CdmaPhoneCallState.PhoneCallState currCdmaThreeWayCallState =
app.cdmaPhoneCallState.getCurrentCallState();
CdmaPhoneCallState.PhoneCallState prevCdmaThreeWayCallState =
app.cdmaPhoneCallState.getPreviousCallState();
log("CDMA call state: " + currCdmaThreeWayCallState + " prev state:" +
prevCdmaThreeWayCallState);
if ((mBluetoothHeadset != null) &&
(mCdmaThreeWayCallState != currCdmaThreeWayCallState)) {
// In CDMA, the network does not provide any feedback
// to the phone when the 2nd MO call goes through the
// stages of DIALING > ALERTING -> ACTIVE we fake the
// sequence
log("CDMA 3way call state change. mNumActive: " + mNumActive +
" mNumHeld: " + mNumHeld + " IsThreeWayCallOrigStateDialing: " +
app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing());
if ((currCdmaThreeWayCallState ==
CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
&& app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
// Mimic dialing, put the call on hold, alerting
mBluetoothHeadset.phoneStateChanged(0, mNumHeld,
convertCallState(Call.State.IDLE, Call.State.DIALING),
mRingNumber.mNumber, mRingNumber.mType);
mBluetoothHeadset.phoneStateChanged(0, mNumHeld,
convertCallState(Call.State.IDLE, Call.State.ALERTING),
mRingNumber.mNumber, mRingNumber.mType);
}
// In CDMA, the network does not provide any feedback to
// the phone when a user merges a 3way call or swaps
// between two calls we need to send a CIEV response
// indicating that a call state got changed which should
// trigger a CLCC update request from the BT client.
if (currCdmaThreeWayCallState ==
CdmaPhoneCallState.PhoneCallState.CONF_CALL &&
prevCdmaThreeWayCallState ==
CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
log("CDMA 3way conf call. mNumActive: " + mNumActive +
" mNumHeld: " + mNumHeld);
mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
convertCallState(Call.State.IDLE, mForegroundCallState),
mRingNumber.mNumber, mRingNumber.mType);
}
}
mCdmaThreeWayCallState = currCdmaThreeWayCallState;
}
} else {
mNumHeld = getNumHeldUmts();
}
boolean callsSwitched = false;
if (mCM.getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA &&
mCdmaThreeWayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
callsSwitched = mCdmaCallsSwapped;
} else {
Call backgroundCall = mCM.getFirstActiveBgCall();
callsSwitched =
(mNumHeld == 1 && ! (backgroundCall.getEarliestConnectTime() ==
mBgndEarliestConnectionTime));
mBgndEarliestConnectionTime = backgroundCall.getEarliestConnectTime();
}
if (mNumActive != oldNumActive || mNumHeld != oldNumHeld ||
mRingingCallState != oldRingingCallState ||
mForegroundCallState != oldForegroundCallState ||
!mRingNumber.equalTo(oldRingNumber) ||
callsSwitched) {
if (mBluetoothHeadset != null) {
mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
convertCallState(mRingingCallState, mForegroundCallState),
mRingNumber.mNumber, mRingNumber.mType);
}
}
}
private void handleListCurrentCalls() {
Phone phone = mCM.getDefaultPhone();
int phoneType = phone.getPhoneType();
// TODO(BT) handle virtual call
if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
listCurrentCallsCdma();
} else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
listCurrentCallsGsm();
} else {
Log.e(TAG, "Unexpected phone type: " + phoneType);
}
// end the result
// when index is 0, other parameter does not matter
mBluetoothHeadset.clccResponse(0, 0, 0, 0, false, "", 0);
}
private void handleQueryPhoneState() {
if (mBluetoothHeadset != null) {
mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
convertCallState(mRingingCallState, mForegroundCallState),
mRingNumber.mNumber, mRingNumber.mType);
}
}
private int getNumHeldUmts() {
int countHeld = 0;
List<Call> heldCalls = mCM.getBackgroundCalls();
for (Call call : heldCalls) {
if (call.getState() == Call.State.HOLDING) {
countHeld++;
}
}
return countHeld;
}
private int getNumHeldCdma() {
int numHeld = 0;
PhoneGlobals app = PhoneGlobals.getInstance();
if (app.cdmaPhoneCallState != null) {
CdmaPhoneCallState.PhoneCallState curr3WayCallState =
app.cdmaPhoneCallState.getCurrentCallState();
CdmaPhoneCallState.PhoneCallState prev3WayCallState =
app.cdmaPhoneCallState.getPreviousCallState();
log("CDMA call state: " + curr3WayCallState + " prev state:" +
prev3WayCallState);
if (curr3WayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
if (prev3WayCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
numHeld = 0; //0: no calls held, as now *both* the caller are active
} else {
numHeld = 1; //1: held call and active call, as on answering a
// Call Waiting, one of the caller *is* put on hold
}
} else if (curr3WayCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
numHeld = 1; //1: held call and active call, as on make a 3 Way Call
// the first caller *is* put on hold
} else {
numHeld = 0; //0: no calls held as this is a SINGLE_ACTIVE call
}
}
return numHeld;
}
private CallNumber getCallNumber(Connection connection, Call call) {
String number = null;
int type = 128;
// find phone number and type
if (connection == null) {
connection = call.getEarliestConnection();
if (connection == null) {
Log.e(TAG, "Could not get a handle on Connection object for the call");
}
}
if (connection != null) {
number = connection.getAddress();
if (number != null) {
type = PhoneNumberUtils.toaFromString(number);
}
}
if (number == null) {
number = "";
}
return new CallNumber(number, type);
}
private class CallNumber
{
private String mNumber = null;
private int mType = 0;
private CallNumber(String number, int type) {
mNumber = number;
mType = type;
}
private boolean equalTo(CallNumber callNumber)
{
if (mType != callNumber.mType) return false;
if (mNumber != null && mNumber.compareTo(callNumber.mNumber) == 0) {
return true;
}
return false;
}
}
private BluetoothProfile.ServiceListener mProfileListener =
new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
mBluetoothHeadset = (BluetoothHeadset) proxy;
}
public void onServiceDisconnected(int profile) {
mBluetoothHeadset = null;
}
};
private void listCurrentCallsGsm() {
// Collect all known connections
// clccConnections isindexed by CLCC index
Connection[] clccConnections = new Connection[GSM_MAX_CONNECTIONS];
LinkedList<Connection> newConnections = new LinkedList<Connection>();
LinkedList<Connection> connections = new LinkedList<Connection>();
Call foregroundCall = mCM.getActiveFgCall();
Call backgroundCall = mCM.getFirstActiveBgCall();
Call ringingCall = mCM.getFirstActiveRingingCall();
if (ringingCall.getState().isAlive()) {
connections.addAll(ringingCall.getConnections());
}
if (foregroundCall.getState().isAlive()) {
connections.addAll(foregroundCall.getConnections());
}
if (backgroundCall.getState().isAlive()) {
connections.addAll(backgroundCall.getConnections());
}
// Mark connections that we already known about
boolean clccUsed[] = new boolean[GSM_MAX_CONNECTIONS];
for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
clccUsed[i] = mClccUsed[i];
mClccUsed[i] = false;
}
for (Connection c : connections) {
boolean found = false;
long timestamp = c.getCreateTime();
for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
if (clccUsed[i] && timestamp == mClccTimestamps[i]) {
mClccUsed[i] = true;
found = true;
clccConnections[i] = c;
break;
}
}
if (!found) {
newConnections.add(c);
}
}
// Find a CLCC index for new connections
while (!newConnections.isEmpty()) {
// Find lowest empty index
int i = 0;
while (mClccUsed[i]) i++;
// Find earliest connection
long earliestTimestamp = newConnections.get(0).getCreateTime();
Connection earliestConnection = newConnections.get(0);
for (int j = 0; j < newConnections.size(); j++) {
long timestamp = newConnections.get(j).getCreateTime();
if (timestamp < earliestTimestamp) {
earliestTimestamp = timestamp;
earliestConnection = newConnections.get(j);
}
}
// update
mClccUsed[i] = true;
mClccTimestamps[i] = earliestTimestamp;
clccConnections[i] = earliestConnection;
newConnections.remove(earliestConnection);
}
// Send CLCC response to Bluetooth headset service
for (int i = 0; i < clccConnections.length; i++) {
if (mClccUsed[i]) {
sendClccResponseGsm(i, clccConnections[i]);
}
}
}
/** Convert a Connection object into a single +CLCC result */
private void sendClccResponseGsm(int index, Connection connection) {
int state = convertCallState(connection.getState());
boolean mpty = false;
Call call = connection.getCall();
if (call != null) {
mpty = call.isMultiparty();
}
int direction = connection.isIncoming() ? 1 : 0;
String number = connection.getAddress();
int type = -1;
if (number != null) {
type = PhoneNumberUtils.toaFromString(number);
}
mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
}
/** Build the +CLCC result for CDMA
* The complexity arises from the fact that we need to maintain the same
* CLCC index even as a call moves between states. */
private synchronized void listCurrentCallsCdma() {
// In CDMA at one time a user can have only two live/active connections
Connection[] clccConnections = new Connection[CDMA_MAX_CONNECTIONS];// indexed by CLCC index
Call foregroundCall = mCM.getActiveFgCall();
Call ringingCall = mCM.getFirstActiveRingingCall();
Call.State ringingCallState = ringingCall.getState();
// If the Ringing Call state is INCOMING, that means this is the very first call
// hence there should not be any Foreground Call
if (ringingCallState == Call.State.INCOMING) {
if (VDBG) log("Filling clccConnections[0] for INCOMING state");
clccConnections[0] = ringingCall.getLatestConnection();
} else if (foregroundCall.getState().isAlive()) {
// Getting Foreground Call connection based on Call state
if (ringingCall.isRinging()) {
if (VDBG) log("Filling clccConnections[0] & [1] for CALL WAITING state");
clccConnections[0] = foregroundCall.getEarliestConnection();
clccConnections[1] = ringingCall.getLatestConnection();
} else {
if (foregroundCall.getConnections().size() <= 1) {
// Single call scenario
if (VDBG) {
log("Filling clccConnections[0] with ForgroundCall latest connection");
}
clccConnections[0] = foregroundCall.getLatestConnection();
} else {
// Multiple Call scenario. This would be true for both
// CONF_CALL and THRWAY_ACTIVE state
if (VDBG) {
log("Filling clccConnections[0] & [1] with ForgroundCall connections");
}
clccConnections[0] = foregroundCall.getEarliestConnection();
clccConnections[1] = foregroundCall.getLatestConnection();
}
}
}
// Update the mCdmaIsSecondCallActive flag based on the Phone call state
if (PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState()
== CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, false);
mHandler.sendMessage(msg);
} else if (PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState()
== CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, true);
mHandler.sendMessage(msg);
}
// send CLCC result
for (int i = 0; (i < clccConnections.length) && (clccConnections[i] != null); i++) {
sendClccResponseCdma(i, clccConnections[i]);
}
}
/** Send ClCC results for a Connection object for CDMA phone */
private void sendClccResponseCdma(int index, Connection connection) {
int state;
PhoneGlobals app = PhoneGlobals.getInstance();
CdmaPhoneCallState.PhoneCallState currCdmaCallState =
app.cdmaPhoneCallState.getCurrentCallState();
CdmaPhoneCallState.PhoneCallState prevCdmaCallState =
app.cdmaPhoneCallState.getPreviousCallState();
if ((prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
&& (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL)) {
// If the current state is reached after merging two calls
// we set the state of all the connections as ACTIVE
state = CALL_STATE_ACTIVE;
} else {
Call.State callState = connection.getState();
switch (callState) {
case ACTIVE:
// For CDMA since both the connections are set as active by FW after accepting
// a Call waiting or making a 3 way call, we need to set the state specifically
// to ACTIVE/HOLDING based on the mCdmaIsSecondCallActive flag. This way the
// CLCC result will allow BT devices to enable the swap or merge options
if (index == 0) { // For the 1st active connection
state = mCdmaIsSecondCallActive ? CALL_STATE_HELD : CALL_STATE_ACTIVE;
} else { // for the 2nd active connection
state = mCdmaIsSecondCallActive ? CALL_STATE_ACTIVE : CALL_STATE_HELD;
}
break;
case HOLDING:
state = CALL_STATE_HELD;
break;
case DIALING:
state = CALL_STATE_DIALING;
break;
case ALERTING:
state = CALL_STATE_ALERTING;
break;
case INCOMING:
state = CALL_STATE_INCOMING;
break;
case WAITING:
state = CALL_STATE_WAITING;
break;
default:
Log.e(TAG, "bad call state: " + callState);
return;
}
}
boolean mpty = false;
if (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
if (prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
// If the current state is reached after merging two calls
// we set the multiparty call true.
mpty = true;
} // else
// CALL_CONF state is not from merging two calls, but from
// accepting the second call. In this case first will be on
// hold in most cases but in some cases its already merged.
// However, we will follow the common case and the test case
// as per Bluetooth SIG PTS
}
int direction = connection.isIncoming() ? 1 : 0;
String number = connection.getAddress();
int type = -1;
if (number != null) {
type = PhoneNumberUtils.toaFromString(number);
} else {
number = "";
}
mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
}
private void handleCdmaSwapSecondCallState() {
if (VDBG) log("cdmaSwapSecondCallState: Toggling mCdmaIsSecondCallActive");
mCdmaIsSecondCallActive = !mCdmaIsSecondCallActive;
mCdmaCallsSwapped = true;
}
private void handleCdmaSetSecondCallState(boolean state) {
if (VDBG) log("cdmaSetSecondCallState: Setting mCdmaIsSecondCallActive to " + state);
mCdmaIsSecondCallActive = state;
if (!mCdmaIsSecondCallActive) {
mCdmaCallsSwapped = false;
}
}
private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
public boolean answerCall() {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
return PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
}
public boolean hangupCall() {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
if (mCM.hasActiveFgCall()) {
return PhoneUtils.hangupActiveCall(mCM.getActiveFgCall());
} else if (mCM.hasActiveRingingCall()) {
return PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
} else if (mCM.hasActiveBgCall()) {
return PhoneUtils.hangupHoldingCall(mCM.getFirstActiveBgCall());
}
// TODO(BT) handle virtual voice call
return false;
}
public boolean sendDtmf(int dtmf) {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
return mCM.sendDtmf((char) dtmf);
}
public boolean processChld(int chld) {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
Phone phone = mCM.getDefaultPhone();
int phoneType = phone.getPhoneType();
Call ringingCall = mCM.getFirstActiveRingingCall();
Call backgroundCall = mCM.getFirstActiveBgCall();
if (chld == CHLD_TYPE_RELEASEHELD) {
if (ringingCall.isRinging()) {
return PhoneUtils.hangupRingingCall(ringingCall);
} else {
return PhoneUtils.hangupHoldingCall(backgroundCall);
}
} else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
if (ringingCall.isRinging()) {
// Hangup the active call and then answer call waiting call.
if (VDBG) log("CHLD:1 Callwaiting Answer call");
PhoneUtils.hangupRingingAndActive(phone);
} else {
// If there is no Call waiting then just hangup
// the active call. In CDMA this mean that the complete
// call session would be ended
if (VDBG) log("CHLD:1 Hangup Call");
PhoneUtils.hangup(PhoneGlobals.getInstance().mCM);
}
return true;
} else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
// Hangup active call, answer held call
return PhoneUtils.answerAndEndActive(PhoneGlobals.getInstance().mCM, ringingCall);
} else {
Log.e(TAG, "bad phone type: " + phoneType);
return false;
}
} else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
// For CDMA, the way we switch to a new incoming call is by
// calling PhoneUtils.answerCall(). switchAndHoldActive() won't
// properly update the call state within telephony.
// If the Phone state is already in CONF_CALL then we simply send
// a flash cmd by calling switchHoldingAndActive()
if (ringingCall.isRinging()) {
if (VDBG) log("CHLD:2 Callwaiting Answer call");
PhoneUtils.answerCall(ringingCall);
PhoneUtils.setMute(false);
// Setting the second callers state flag to TRUE (i.e. active)
cdmaSetSecondCallState(true);
return true;
} else if (PhoneGlobals.getInstance().cdmaPhoneCallState
.getCurrentCallState()
== CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
if (VDBG) log("CHLD:2 Swap Calls");
PhoneUtils.switchHoldingAndActive(backgroundCall);
// Toggle the second callers active state flag
cdmaSwapSecondCallState();
return true;
}
Log.e(TAG, "CDMA fail to do hold active and accept held");
return false;
} else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
PhoneUtils.switchHoldingAndActive(backgroundCall);
return true;
} else {
Log.e(TAG, "Unexpected phone type: " + phoneType);
return false;
}
} else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
CdmaPhoneCallState.PhoneCallState state =
PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState();
// For CDMA, we need to check if the call is in THRWAY_ACTIVE state
if (state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
if (VDBG) log("CHLD:3 Merge Calls");
PhoneUtils.mergeCalls();
return true;
} else if (state == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
// State is CONF_CALL already and we are getting a merge call
// This can happen when CONF_CALL was entered from a Call Waiting
// TODO(BT)
return false;
}
Log.e(TAG, "GSG no call to add conference");
return false;
} else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
if (mCM.hasActiveFgCall() && mCM.hasActiveBgCall()) {
PhoneUtils.mergeCalls();
return true;
} else {
Log.e(TAG, "GSG no call to merge");
return false;
}
} else {
Log.e(TAG, "Unexpected phone type: " + phoneType);
return false;
}
} else {
Log.e(TAG, "bad CHLD value: " + chld);
return false;
}
}
public String getNetworkOperator() {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
return mCM.getDefaultPhone().getServiceState().getOperatorAlphaLong();
}
public String getSubscriberNumber() {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
return mCM.getDefaultPhone().getLine1Number();
}
public boolean listCurrentCalls() {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
Message msg = Message.obtain(mHandler, LIST_CURRENT_CALLS);
mHandler.sendMessage(msg);
return true;
}
public boolean queryPhoneState() {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
Message msg = Message.obtain(mHandler, QUERY_PHONE_STATE);
mHandler.sendMessage(msg);
return true;
}
public void updateBtHandsfreeAfterRadioTechnologyChange() {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
if (VDBG) Log.d(TAG, "updateBtHandsfreeAfterRadioTechnologyChange...");
updateBtPhoneStateAfterRadioTechnologyChange();
}
public void cdmaSwapSecondCallState() {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
Message msg = Message.obtain(mHandler, CDMA_SWAP_SECOND_CALL_STATE);
mHandler.sendMessage(msg);
}
public void cdmaSetSecondCallState(boolean state) {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, state);
mHandler.sendMessage(msg);
}
};
// match up with bthf_call_state_t of bt_hf.h
final static int CALL_STATE_ACTIVE = 0;
final static int CALL_STATE_HELD = 1;
final static int CALL_STATE_DIALING = 2;
final static int CALL_STATE_ALERTING = 3;
final static int CALL_STATE_INCOMING = 4;
final static int CALL_STATE_WAITING = 5;
final static int CALL_STATE_IDLE = 6;
// match up with bthf_chld_type_t of bt_hf.h
final static int CHLD_TYPE_RELEASEHELD = 0;
final static int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
final static int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
final static int CHLD_TYPE_ADDHELDTOCONF = 3;
/* Convert telephony phone call state into hf hal call state */
static int convertCallState(Call.State ringingState, Call.State foregroundState) {
if ((ringingState == Call.State.INCOMING) ||
(ringingState == Call.State.WAITING) )
return CALL_STATE_INCOMING;
else if (foregroundState == Call.State.DIALING)
return CALL_STATE_DIALING;
else if (foregroundState == Call.State.ALERTING)
return CALL_STATE_ALERTING;
else
return CALL_STATE_IDLE;
}
static int convertCallState(Call.State callState) {
switch (callState) {
case IDLE:
case DISCONNECTED:
case DISCONNECTING:
return CALL_STATE_IDLE;
case ACTIVE:
return CALL_STATE_ACTIVE;
case HOLDING:
return CALL_STATE_HELD;
case DIALING:
return CALL_STATE_DIALING;
case ALERTING:
return CALL_STATE_ALERTING;
case INCOMING:
return CALL_STATE_INCOMING;
case WAITING:
return CALL_STATE_WAITING;
default:
Log.e(TAG, "bad call state: " + callState);
return CALL_STATE_IDLE;
}
}
private static void log(String msg) {
Log.d(TAG, msg);
}
}