Java程序  |  957行  |  39.21 KB

/*
 * 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 com.android.phone.CallGatewayManager.RawGatewayInfo;

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 CallGatewayManager mCallGatewayManager;

    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;

    // 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;
        }
        mCallGatewayManager = CallGatewayManager.getInstance();

        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);;

        handlePreciseCallStateChange(null);

        if(VDBG) Log.d(TAG, "registerForServiceStateChanged");
        // register for updates
        mCM.registerForPreciseCallStateChanged(mHandler, PRECISE_CALL_STATE_CHANGED, null);
        mCM.registerForCallWaiting(mHandler, PHONE_CDMA_CALL_WAITING, null);
        mCM.registerForDisconnect(mHandler, PHONE_ON_DISCONNECT, null);

        // TODO(BT) registerForIncomingRing?
        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 PRECISE_CALL_STATE_CHANGED = 1;
    private static final int PHONE_CDMA_CALL_WAITING = 2;
    private static final int LIST_CURRENT_CALLS = 3;
    private static final int QUERY_PHONE_STATE = 4;
    private static final int CDMA_SWAP_SECOND_CALL_STATE = 5;
    private static final int CDMA_SET_SECOND_CALL_STATE = 6;
    private static final int PHONE_ON_DISCONNECT = 7;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (VDBG) Log.d(TAG, "handleMessage: " + msg.what);
            switch(msg.what) {
                case PRECISE_CALL_STATE_CHANGED:
                case PHONE_CDMA_CALL_WAITING:
                case PHONE_ON_DISCONNECT:
                    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.unregisterForPreciseCallStateChanged(mHandler);
        mCM.unregisterForCallWaiting(mHandler);

        //Register all events new to the new active phone
        mCM.registerForPreciseCallStateChanged(mHandler,
                                               PRECISE_CALL_STATE_CHANGED, null);
        mCM.registerForCallWaiting(mHandler,
                                   PHONE_CDMA_CALL_WAITING, null);
    }

    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
        if (mBluetoothHeadset != null) {
            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();
        }

        boolean isIncoming = connection.isIncoming();

        // For GV outgoing calls send the contact phone #, not the gateway #.
        String number = connection.getAddress();
        if (!isIncoming) {
            RawGatewayInfo rawInfo = mCallGatewayManager.getGatewayInfo(connection);
            if (!rawInfo.isEmpty()) {
                number = rawInfo.trueNumber;
            }
        }
        int type = -1;
        if (number != null) {
            type = PhoneNumberUtils.toaFromString(number);
        } else {
            number = "";
        }

        if (mBluetoothHeadset != null) {
            mBluetoothHeadset.clccResponse(index + 1, isIncoming ? 1 : 0,
                    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
        }

        boolean isIncoming = connection.isIncoming();

        // For GV outgoing calls send the contact phone #, not the gateway #.
        String number = connection.getAddress();
        if (!isIncoming) {
            RawGatewayInfo rawInfo = mCallGatewayManager.getGatewayInfo(connection);
            if (!rawInfo.isEmpty()) {
                number = rawInfo.trueNumber;
            }
        }
        int type = -1;
        if (number != null) {
            type = PhoneNumberUtils.toaFromString(number);
        } else {
            number = "";
        }

        if (mBluetoothHeadset != null) {
            mBluetoothHeadset.clccResponse(index + 1, isIncoming ? 1 : 0,
                    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) {
        int retval = CALL_STATE_IDLE;

        if ((ringingState == Call.State.INCOMING) ||
            (ringingState == Call.State.WAITING) )
            retval = CALL_STATE_INCOMING;
        else if (foregroundState == Call.State.DIALING)
            retval = CALL_STATE_DIALING;
        else if (foregroundState == Call.State.ALERTING)
            retval = CALL_STATE_ALERTING;
        else
            retval = CALL_STATE_IDLE;

        if (VDBG) {
            Log.v(TAG, "Call state Converted2: " + ringingState + "/" + foregroundState + " -> " +
                    retval);
        }
        return retval;
    }

    static int convertCallState(Call.State callState) {
        int retval = CALL_STATE_IDLE;

        switch (callState) {
        case IDLE:
        case DISCONNECTED:
        case DISCONNECTING:
            retval = CALL_STATE_IDLE;
            break;
        case ACTIVE:
            retval = CALL_STATE_ACTIVE;
            break;
        case HOLDING:
            retval = CALL_STATE_HELD;
            break;
        case DIALING:
            retval = CALL_STATE_DIALING;
            break;
        case ALERTING:
            retval = CALL_STATE_ALERTING;
            break;
        case INCOMING:
            retval = CALL_STATE_INCOMING;
            break;
        case WAITING:
            retval = CALL_STATE_WAITING;
            break;
        default:
            Log.e(TAG, "bad call state: " + callState);
            retval = CALL_STATE_IDLE;
            break;
        }

        if (VDBG) {
            Log.v(TAG, "Call state Converted2: " + callState + " -> " + retval);
        }

        return retval;
    }

    private static void log(String msg) {
        Log.d(TAG, msg);
    }
}