Blanket copy of PhoneApp to services/Telephony.

First phase of splitting out InCallUI from PhoneApp.

Change-Id: I237341c4ff00e96c677caa4580b251ef3432931b
diff --git a/src/com/android/phone/BluetoothPhoneService.java b/src/com/android/phone/BluetoothPhoneService.java
new file mode 100644
index 0000000..aff6bf2
--- /dev/null
+++ b/src/com/android/phone/BluetoothPhoneService.java
@@ -0,0 +1,903 @@
+/*
+ * 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;
+
+    // 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);;
+
+        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);
+        // 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 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 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:
+                    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
+        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);
+    }
+}