1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.phone;
18
19import android.app.Service;
20import android.bluetooth.BluetoothAdapter;
21import android.bluetooth.BluetoothDevice;
22import android.bluetooth.BluetoothHeadset;
23import android.bluetooth.BluetoothProfile;
24import android.bluetooth.IBluetoothHeadsetPhone;
25import android.content.Context;
26import android.content.Intent;
27import android.os.AsyncResult;
28import android.os.Handler;
29import android.os.IBinder;
30import android.os.Message;
31import android.os.PowerManager;
32import android.os.PowerManager.WakeLock;
33import android.os.SystemProperties;
34import android.telephony.PhoneNumberUtils;
35import android.telephony.ServiceState;
36import android.util.Log;
37
38import com.android.internal.telephony.Call;
39import com.android.internal.telephony.Connection;
40import com.android.internal.telephony.Phone;
41import com.android.internal.telephony.PhoneConstants;
42import com.android.internal.telephony.TelephonyIntents;
43import com.android.internal.telephony.CallManager;
44
45import java.io.IOException;
46import java.util.LinkedList;
47import java.util.List;
48
49/**
50 * Bluetooth headset manager for the Phone app.
51 * @hide
52 */
53public class BluetoothPhoneService extends Service {
54    private static final String TAG = "BluetoothPhoneService";
55    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1)
56            && (SystemProperties.getInt("ro.debuggable", 0) == 1);
57    private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);  // even more logging
58
59    private static final String MODIFY_PHONE_STATE = android.Manifest.permission.MODIFY_PHONE_STATE;
60
61    private BluetoothAdapter mAdapter;
62    private CallManager mCM;
63
64    private BluetoothHeadset mBluetoothHeadset;
65
66    private PowerManager mPowerManager;
67
68    private WakeLock mStartCallWakeLock;  // held while waiting for the intent to start call
69
70    private PhoneConstants.State mPhoneState = PhoneConstants.State.IDLE;
71    CdmaPhoneCallState.PhoneCallState mCdmaThreeWayCallState =
72                                            CdmaPhoneCallState.PhoneCallState.IDLE;
73
74    private Call.State mForegroundCallState;
75    private Call.State mRingingCallState;
76    private CallNumber mRingNumber;
77    // number of active calls
78    int mNumActive;
79    // number of background (held) calls
80    int mNumHeld;
81
82    long mBgndEarliestConnectionTime = 0;
83
84    // CDMA specific flag used in context with BT devices having display capabilities
85    // to show which Caller is active. This state might not be always true as in CDMA
86    // networks if a caller drops off no update is provided to the Phone.
87    // This flag is just used as a toggle to provide a update to the BT device to specify
88    // which caller is active.
89    private boolean mCdmaIsSecondCallActive = false;
90    private boolean mCdmaCallsSwapped = false;
91
92    private long[] mClccTimestamps; // Timestamps associated with each clcc index
93    private boolean[] mClccUsed;     // Is this clcc index in use
94
95    private static final int GSM_MAX_CONNECTIONS = 6;  // Max connections allowed by GSM
96    private static final int CDMA_MAX_CONNECTIONS = 2;  // Max connections allowed by CDMA
97
98    @Override
99    public void onCreate() {
100        super.onCreate();
101        mCM = CallManager.getInstance();
102        mAdapter = BluetoothAdapter.getDefaultAdapter();
103        if (mAdapter == null) {
104            if (VDBG) Log.d(TAG, "mAdapter null");
105            return;
106        }
107
108        mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
109        mStartCallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
110                                                       TAG + ":StartCall");
111        mStartCallWakeLock.setReferenceCounted(false);
112
113        mAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET);
114
115        mForegroundCallState = Call.State.IDLE;
116        mRingingCallState = Call.State.IDLE;
117        mNumActive = 0;
118        mNumHeld = 0;
119        mRingNumber = new CallNumber("", 0);;
120
121        handlePreciseCallStateChange(null);
122
123        if(VDBG) Log.d(TAG, "registerForServiceStateChanged");
124        // register for updates
125        mCM.registerForPreciseCallStateChanged(mHandler, PRECISE_CALL_STATE_CHANGED, null);
126        mCM.registerForCallWaiting(mHandler, PHONE_CDMA_CALL_WAITING, null);
127        mCM.registerForDisconnect(mHandler, PHONE_ON_DISCONNECT, null);
128
129        // TODO(BT) registerForIncomingRing?
130        mClccTimestamps = new long[GSM_MAX_CONNECTIONS];
131        mClccUsed = new boolean[GSM_MAX_CONNECTIONS];
132        for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
133            mClccUsed[i] = false;
134        }
135    }
136
137    @Override
138    public void onStart(Intent intent, int startId) {
139        if (mAdapter == null) {
140            Log.w(TAG, "Stopping Bluetooth BluetoothPhoneService Service: device does not have BT");
141            stopSelf();
142        }
143        if (VDBG) Log.d(TAG, "BluetoothPhoneService started");
144    }
145
146    @Override
147    public void onDestroy() {
148        super.onDestroy();
149        if (DBG) log("Stopping Bluetooth BluetoothPhoneService Service");
150    }
151
152    @Override
153    public IBinder onBind(Intent intent) {
154        return mBinder;
155    }
156
157    private static final int PRECISE_CALL_STATE_CHANGED = 1;
158    private static final int PHONE_CDMA_CALL_WAITING = 2;
159    private static final int LIST_CURRENT_CALLS = 3;
160    private static final int QUERY_PHONE_STATE = 4;
161    private static final int CDMA_SWAP_SECOND_CALL_STATE = 5;
162    private static final int CDMA_SET_SECOND_CALL_STATE = 6;
163    private static final int PHONE_ON_DISCONNECT = 7;
164
165    private Handler mHandler = new Handler() {
166        @Override
167        public void handleMessage(Message msg) {
168            if (VDBG) Log.d(TAG, "handleMessage: " + msg.what);
169            switch(msg.what) {
170                case PRECISE_CALL_STATE_CHANGED:
171                case PHONE_CDMA_CALL_WAITING:
172                case PHONE_ON_DISCONNECT:
173                    Connection connection = null;
174                    if (((AsyncResult) msg.obj).result instanceof Connection) {
175                        connection = (Connection) ((AsyncResult) msg.obj).result;
176                    }
177                    handlePreciseCallStateChange(connection);
178                    break;
179                case LIST_CURRENT_CALLS:
180                    handleListCurrentCalls();
181                    break;
182                case QUERY_PHONE_STATE:
183                    handleQueryPhoneState();
184                    break;
185                case CDMA_SWAP_SECOND_CALL_STATE:
186                    handleCdmaSwapSecondCallState();
187                    break;
188                case CDMA_SET_SECOND_CALL_STATE:
189                    handleCdmaSetSecondCallState((Boolean) msg.obj);
190                    break;
191            }
192        }
193    };
194
195    private void updateBtPhoneStateAfterRadioTechnologyChange() {
196        if(VDBG) Log.d(TAG, "updateBtPhoneStateAfterRadioTechnologyChange...");
197
198        //Unregister all events from the old obsolete phone
199        mCM.unregisterForPreciseCallStateChanged(mHandler);
200        mCM.unregisterForCallWaiting(mHandler);
201
202        //Register all events new to the new active phone
203        mCM.registerForPreciseCallStateChanged(mHandler,
204                                               PRECISE_CALL_STATE_CHANGED, null);
205        mCM.registerForCallWaiting(mHandler,
206                                   PHONE_CDMA_CALL_WAITING, null);
207    }
208
209    private void handlePreciseCallStateChange(Connection connection) {
210        // get foreground call state
211        int oldNumActive = mNumActive;
212        int oldNumHeld = mNumHeld;
213        Call.State oldRingingCallState = mRingingCallState;
214        Call.State oldForegroundCallState = mForegroundCallState;
215        CallNumber oldRingNumber = mRingNumber;
216
217        Call foregroundCall = mCM.getActiveFgCall();
218
219        if (VDBG)
220            Log.d(TAG, " handlePreciseCallStateChange: foreground: " + foregroundCall +
221                " background: " + mCM.getFirstActiveBgCall() + " ringing: " +
222                mCM.getFirstActiveRingingCall());
223
224        mForegroundCallState = foregroundCall.getState();
225        /* if in transition, do not update */
226        if (mForegroundCallState == Call.State.DISCONNECTING)
227        {
228            Log.d(TAG, "handlePreciseCallStateChange. Call disconnecting, wait before update");
229            return;
230        }
231        else
232            mNumActive = (mForegroundCallState == Call.State.ACTIVE) ? 1 : 0;
233
234        Call ringingCall = mCM.getFirstActiveRingingCall();
235        mRingingCallState = ringingCall.getState();
236        mRingNumber = getCallNumber(connection, ringingCall);
237
238        if (mCM.getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
239            mNumHeld = getNumHeldCdma();
240            PhoneGlobals app = PhoneGlobals.getInstance();
241            if (app.cdmaPhoneCallState != null) {
242                CdmaPhoneCallState.PhoneCallState currCdmaThreeWayCallState =
243                        app.cdmaPhoneCallState.getCurrentCallState();
244                CdmaPhoneCallState.PhoneCallState prevCdmaThreeWayCallState =
245                    app.cdmaPhoneCallState.getPreviousCallState();
246
247                log("CDMA call state: " + currCdmaThreeWayCallState + " prev state:" +
248                    prevCdmaThreeWayCallState);
249
250                if ((mBluetoothHeadset != null) &&
251                    (mCdmaThreeWayCallState != currCdmaThreeWayCallState)) {
252                    // In CDMA, the network does not provide any feedback
253                    // to the phone when the 2nd MO call goes through the
254                    // stages of DIALING > ALERTING -> ACTIVE we fake the
255                    // sequence
256                    log("CDMA 3way call state change. mNumActive: " + mNumActive +
257                        " mNumHeld: " + mNumHeld + " IsThreeWayCallOrigStateDialing: " +
258                        app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing());
259                    if ((currCdmaThreeWayCallState ==
260                            CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
261                                && app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
262                        // Mimic dialing, put the call on hold, alerting
263                        mBluetoothHeadset.phoneStateChanged(0, mNumHeld,
264                            convertCallState(Call.State.IDLE, Call.State.DIALING),
265                            mRingNumber.mNumber, mRingNumber.mType);
266
267                        mBluetoothHeadset.phoneStateChanged(0, mNumHeld,
268                            convertCallState(Call.State.IDLE, Call.State.ALERTING),
269                            mRingNumber.mNumber, mRingNumber.mType);
270
271                    }
272
273                    // In CDMA, the network does not provide any feedback to
274                    // the phone when a user merges a 3way call or swaps
275                    // between two calls we need to send a CIEV response
276                    // indicating that a call state got changed which should
277                    // trigger a CLCC update request from the BT client.
278                    if (currCdmaThreeWayCallState ==
279                            CdmaPhoneCallState.PhoneCallState.CONF_CALL &&
280                            prevCdmaThreeWayCallState ==
281                              CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
282                        log("CDMA 3way conf call. mNumActive: " + mNumActive +
283                            " mNumHeld: " + mNumHeld);
284                        mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
285                            convertCallState(Call.State.IDLE, mForegroundCallState),
286                            mRingNumber.mNumber, mRingNumber.mType);
287                    }
288                }
289                mCdmaThreeWayCallState = currCdmaThreeWayCallState;
290            }
291        } else {
292            mNumHeld = getNumHeldUmts();
293        }
294
295        boolean callsSwitched = false;
296        if (mCM.getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA &&
297            mCdmaThreeWayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
298            callsSwitched = mCdmaCallsSwapped;
299        } else {
300            Call backgroundCall = mCM.getFirstActiveBgCall();
301            callsSwitched =
302                (mNumHeld == 1 && ! (backgroundCall.getEarliestConnectTime() ==
303                    mBgndEarliestConnectionTime));
304            mBgndEarliestConnectionTime = backgroundCall.getEarliestConnectTime();
305        }
306
307        if (mNumActive != oldNumActive || mNumHeld != oldNumHeld ||
308            mRingingCallState != oldRingingCallState ||
309            mForegroundCallState != oldForegroundCallState ||
310            !mRingNumber.equalTo(oldRingNumber) ||
311            callsSwitched) {
312            if (mBluetoothHeadset != null) {
313                mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
314                    convertCallState(mRingingCallState, mForegroundCallState),
315                    mRingNumber.mNumber, mRingNumber.mType);
316            }
317        }
318    }
319
320    private void handleListCurrentCalls() {
321        Phone phone = mCM.getDefaultPhone();
322        int phoneType = phone.getPhoneType();
323
324        // TODO(BT) handle virtual call
325
326        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
327            listCurrentCallsCdma();
328        } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
329            listCurrentCallsGsm();
330        } else {
331            Log.e(TAG, "Unexpected phone type: " + phoneType);
332        }
333        // end the result
334        // when index is 0, other parameter does not matter
335        mBluetoothHeadset.clccResponse(0, 0, 0, 0, false, "", 0);
336    }
337
338    private void handleQueryPhoneState() {
339        if (mBluetoothHeadset != null) {
340            mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
341                convertCallState(mRingingCallState, mForegroundCallState),
342                mRingNumber.mNumber, mRingNumber.mType);
343        }
344    }
345
346    private int getNumHeldUmts() {
347        int countHeld = 0;
348        List<Call> heldCalls = mCM.getBackgroundCalls();
349
350        for (Call call : heldCalls) {
351            if (call.getState() == Call.State.HOLDING) {
352                countHeld++;
353            }
354        }
355        return countHeld;
356    }
357
358    private int getNumHeldCdma() {
359        int numHeld = 0;
360        PhoneGlobals app = PhoneGlobals.getInstance();
361        if (app.cdmaPhoneCallState != null) {
362            CdmaPhoneCallState.PhoneCallState curr3WayCallState =
363                app.cdmaPhoneCallState.getCurrentCallState();
364            CdmaPhoneCallState.PhoneCallState prev3WayCallState =
365                app.cdmaPhoneCallState.getPreviousCallState();
366
367            log("CDMA call state: " + curr3WayCallState + " prev state:" +
368                prev3WayCallState);
369            if (curr3WayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
370                if (prev3WayCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
371                    numHeld = 0; //0: no calls held, as now *both* the caller are active
372                } else {
373                    numHeld = 1; //1: held call and active call, as on answering a
374                    // Call Waiting, one of the caller *is* put on hold
375                }
376            } else if (curr3WayCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
377                numHeld = 1; //1: held call and active call, as on make a 3 Way Call
378                // the first caller *is* put on hold
379            } else {
380                numHeld = 0; //0: no calls held as this is a SINGLE_ACTIVE call
381            }
382        }
383        return numHeld;
384    }
385
386    private CallNumber getCallNumber(Connection connection, Call call) {
387        String number = null;
388        int type = 128;
389        // find phone number and type
390        if (connection == null) {
391            connection = call.getEarliestConnection();
392            if (connection == null) {
393                Log.e(TAG, "Could not get a handle on Connection object for the call");
394            }
395        }
396        if (connection != null) {
397            number = connection.getAddress();
398            if (number != null) {
399                type = PhoneNumberUtils.toaFromString(number);
400            }
401        }
402        if (number == null) {
403            number = "";
404        }
405        return new CallNumber(number, type);
406    }
407
408    private class CallNumber
409    {
410        private String mNumber = null;
411        private int mType = 0;
412
413        private CallNumber(String number, int type) {
414            mNumber = number;
415            mType = type;
416        }
417
418        private boolean equalTo(CallNumber callNumber)
419        {
420            if (mType != callNumber.mType) return false;
421
422            if (mNumber != null && mNumber.compareTo(callNumber.mNumber) == 0) {
423                return true;
424            }
425            return false;
426        }
427    }
428
429    private BluetoothProfile.ServiceListener mProfileListener =
430            new BluetoothProfile.ServiceListener() {
431        public void onServiceConnected(int profile, BluetoothProfile proxy) {
432            mBluetoothHeadset = (BluetoothHeadset) proxy;
433        }
434        public void onServiceDisconnected(int profile) {
435            mBluetoothHeadset = null;
436        }
437    };
438
439    private void listCurrentCallsGsm() {
440        // Collect all known connections
441        // clccConnections isindexed by CLCC index
442        Connection[] clccConnections = new Connection[GSM_MAX_CONNECTIONS];
443        LinkedList<Connection> newConnections = new LinkedList<Connection>();
444        LinkedList<Connection> connections = new LinkedList<Connection>();
445
446        Call foregroundCall = mCM.getActiveFgCall();
447        Call backgroundCall = mCM.getFirstActiveBgCall();
448        Call ringingCall = mCM.getFirstActiveRingingCall();
449
450        if (ringingCall.getState().isAlive()) {
451            connections.addAll(ringingCall.getConnections());
452        }
453        if (foregroundCall.getState().isAlive()) {
454            connections.addAll(foregroundCall.getConnections());
455        }
456        if (backgroundCall.getState().isAlive()) {
457            connections.addAll(backgroundCall.getConnections());
458        }
459
460        // Mark connections that we already known about
461        boolean clccUsed[] = new boolean[GSM_MAX_CONNECTIONS];
462        for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
463            clccUsed[i] = mClccUsed[i];
464            mClccUsed[i] = false;
465        }
466        for (Connection c : connections) {
467            boolean found = false;
468            long timestamp = c.getCreateTime();
469            for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
470                if (clccUsed[i] && timestamp == mClccTimestamps[i]) {
471                    mClccUsed[i] = true;
472                    found = true;
473                    clccConnections[i] = c;
474                    break;
475                }
476            }
477            if (!found) {
478                newConnections.add(c);
479            }
480        }
481
482        // Find a CLCC index for new connections
483        while (!newConnections.isEmpty()) {
484            // Find lowest empty index
485            int i = 0;
486            while (mClccUsed[i]) i++;
487            // Find earliest connection
488            long earliestTimestamp = newConnections.get(0).getCreateTime();
489            Connection earliestConnection = newConnections.get(0);
490            for (int j = 0; j < newConnections.size(); j++) {
491                long timestamp = newConnections.get(j).getCreateTime();
492                if (timestamp < earliestTimestamp) {
493                    earliestTimestamp = timestamp;
494                    earliestConnection = newConnections.get(j);
495                }
496            }
497
498            // update
499            mClccUsed[i] = true;
500            mClccTimestamps[i] = earliestTimestamp;
501            clccConnections[i] = earliestConnection;
502            newConnections.remove(earliestConnection);
503        }
504
505        // Send CLCC response to Bluetooth headset service
506        for (int i = 0; i < clccConnections.length; i++) {
507            if (mClccUsed[i]) {
508                sendClccResponseGsm(i, clccConnections[i]);
509            }
510        }
511    }
512
513    /** Convert a Connection object into a single +CLCC result */
514    private void sendClccResponseGsm(int index, Connection connection) {
515        int state = convertCallState(connection.getState());
516        boolean mpty = false;
517        Call call = connection.getCall();
518        if (call != null) {
519            mpty = call.isMultiparty();
520        }
521
522        int direction = connection.isIncoming() ? 1 : 0;
523
524        String number = connection.getAddress();
525        int type = -1;
526        if (number != null) {
527            type = PhoneNumberUtils.toaFromString(number);
528        }
529
530        mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
531    }
532
533    /** Build the +CLCC result for CDMA
534     *  The complexity arises from the fact that we need to maintain the same
535     *  CLCC index even as a call moves between states. */
536    private synchronized void listCurrentCallsCdma() {
537        // In CDMA at one time a user can have only two live/active connections
538        Connection[] clccConnections = new Connection[CDMA_MAX_CONNECTIONS];// indexed by CLCC index
539        Call foregroundCall = mCM.getActiveFgCall();
540        Call ringingCall = mCM.getFirstActiveRingingCall();
541
542        Call.State ringingCallState = ringingCall.getState();
543        // If the Ringing Call state is INCOMING, that means this is the very first call
544        // hence there should not be any Foreground Call
545        if (ringingCallState == Call.State.INCOMING) {
546            if (VDBG) log("Filling clccConnections[0] for INCOMING state");
547            clccConnections[0] = ringingCall.getLatestConnection();
548        } else if (foregroundCall.getState().isAlive()) {
549            // Getting Foreground Call connection based on Call state
550            if (ringingCall.isRinging()) {
551                if (VDBG) log("Filling clccConnections[0] & [1] for CALL WAITING state");
552                clccConnections[0] = foregroundCall.getEarliestConnection();
553                clccConnections[1] = ringingCall.getLatestConnection();
554            } else {
555                if (foregroundCall.getConnections().size() <= 1) {
556                    // Single call scenario
557                    if (VDBG) {
558                        log("Filling clccConnections[0] with ForgroundCall latest connection");
559                    }
560                    clccConnections[0] = foregroundCall.getLatestConnection();
561                } else {
562                    // Multiple Call scenario. This would be true for both
563                    // CONF_CALL and THRWAY_ACTIVE state
564                    if (VDBG) {
565                        log("Filling clccConnections[0] & [1] with ForgroundCall connections");
566                    }
567                    clccConnections[0] = foregroundCall.getEarliestConnection();
568                    clccConnections[1] = foregroundCall.getLatestConnection();
569                }
570            }
571        }
572
573        // Update the mCdmaIsSecondCallActive flag based on the Phone call state
574        if (PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState()
575                == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
576            Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, false);
577            mHandler.sendMessage(msg);
578        } else if (PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState()
579                == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
580            Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, true);
581            mHandler.sendMessage(msg);
582        }
583
584        // send CLCC result
585        for (int i = 0; (i < clccConnections.length) && (clccConnections[i] != null); i++) {
586            sendClccResponseCdma(i, clccConnections[i]);
587        }
588    }
589
590    /** Send ClCC results for a Connection object for CDMA phone */
591    private void sendClccResponseCdma(int index, Connection connection) {
592        int state;
593        PhoneGlobals app = PhoneGlobals.getInstance();
594        CdmaPhoneCallState.PhoneCallState currCdmaCallState =
595                app.cdmaPhoneCallState.getCurrentCallState();
596        CdmaPhoneCallState.PhoneCallState prevCdmaCallState =
597                app.cdmaPhoneCallState.getPreviousCallState();
598
599        if ((prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
600                && (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL)) {
601            // If the current state is reached after merging two calls
602            // we set the state of all the connections as ACTIVE
603            state = CALL_STATE_ACTIVE;
604        } else {
605            Call.State callState = connection.getState();
606            switch (callState) {
607            case ACTIVE:
608                // For CDMA since both the connections are set as active by FW after accepting
609                // a Call waiting or making a 3 way call, we need to set the state specifically
610                // to ACTIVE/HOLDING based on the mCdmaIsSecondCallActive flag. This way the
611                // CLCC result will allow BT devices to enable the swap or merge options
612                if (index == 0) { // For the 1st active connection
613                    state = mCdmaIsSecondCallActive ? CALL_STATE_HELD : CALL_STATE_ACTIVE;
614                } else { // for the 2nd active connection
615                    state = mCdmaIsSecondCallActive ? CALL_STATE_ACTIVE : CALL_STATE_HELD;
616                }
617                break;
618            case HOLDING:
619                state = CALL_STATE_HELD;
620                break;
621            case DIALING:
622                state = CALL_STATE_DIALING;
623                break;
624            case ALERTING:
625                state = CALL_STATE_ALERTING;
626                break;
627            case INCOMING:
628                state = CALL_STATE_INCOMING;
629                break;
630            case WAITING:
631                state = CALL_STATE_WAITING;
632                break;
633            default:
634                Log.e(TAG, "bad call state: " + callState);
635                return;
636            }
637        }
638
639        boolean mpty = false;
640        if (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
641            if (prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
642                // If the current state is reached after merging two calls
643                // we set the multiparty call true.
644                mpty = true;
645            } // else
646                // CALL_CONF state is not from merging two calls, but from
647                // accepting the second call. In this case first will be on
648                // hold in most cases but in some cases its already merged.
649                // However, we will follow the common case and the test case
650                // as per Bluetooth SIG PTS
651        }
652
653        int direction = connection.isIncoming() ? 1 : 0;
654
655        String number = connection.getAddress();
656        int type = -1;
657        if (number != null) {
658            type = PhoneNumberUtils.toaFromString(number);
659        } else {
660            number = "";
661        }
662
663        mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
664    }
665
666    private void handleCdmaSwapSecondCallState() {
667        if (VDBG) log("cdmaSwapSecondCallState: Toggling mCdmaIsSecondCallActive");
668        mCdmaIsSecondCallActive = !mCdmaIsSecondCallActive;
669        mCdmaCallsSwapped = true;
670    }
671
672    private void handleCdmaSetSecondCallState(boolean state) {
673        if (VDBG) log("cdmaSetSecondCallState: Setting mCdmaIsSecondCallActive to " + state);
674        mCdmaIsSecondCallActive = state;
675
676        if (!mCdmaIsSecondCallActive) {
677            mCdmaCallsSwapped = false;
678        }
679    }
680
681    private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
682        public boolean answerCall() {
683            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
684            return PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
685        }
686
687        public boolean hangupCall() {
688            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
689            if (mCM.hasActiveFgCall()) {
690                return PhoneUtils.hangupActiveCall(mCM.getActiveFgCall());
691            } else if (mCM.hasActiveRingingCall()) {
692                return PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
693            } else if (mCM.hasActiveBgCall()) {
694                return PhoneUtils.hangupHoldingCall(mCM.getFirstActiveBgCall());
695            }
696            // TODO(BT) handle virtual voice call
697            return false;
698        }
699
700        public boolean sendDtmf(int dtmf) {
701            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
702            return mCM.sendDtmf((char) dtmf);
703        }
704
705        public boolean processChld(int chld) {
706            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
707            Phone phone = mCM.getDefaultPhone();
708            int phoneType = phone.getPhoneType();
709            Call ringingCall = mCM.getFirstActiveRingingCall();
710            Call backgroundCall = mCM.getFirstActiveBgCall();
711
712            if (chld == CHLD_TYPE_RELEASEHELD) {
713                if (ringingCall.isRinging()) {
714                    return PhoneUtils.hangupRingingCall(ringingCall);
715                } else {
716                    return PhoneUtils.hangupHoldingCall(backgroundCall);
717                }
718            } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
719                if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
720                    if (ringingCall.isRinging()) {
721                        // Hangup the active call and then answer call waiting call.
722                        if (VDBG) log("CHLD:1 Callwaiting Answer call");
723                        PhoneUtils.hangupRingingAndActive(phone);
724                    } else {
725                        // If there is no Call waiting then just hangup
726                        // the active call. In CDMA this mean that the complete
727                        // call session would be ended
728                        if (VDBG) log("CHLD:1 Hangup Call");
729                        PhoneUtils.hangup(PhoneGlobals.getInstance().mCM);
730                    }
731                    return true;
732                } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
733                    // Hangup active call, answer held call
734                    return PhoneUtils.answerAndEndActive(PhoneGlobals.getInstance().mCM, ringingCall);
735                } else {
736                    Log.e(TAG, "bad phone type: " + phoneType);
737                    return false;
738                }
739            } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
740                if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
741                    // For CDMA, the way we switch to a new incoming call is by
742                    // calling PhoneUtils.answerCall(). switchAndHoldActive() won't
743                    // properly update the call state within telephony.
744                    // If the Phone state is already in CONF_CALL then we simply send
745                    // a flash cmd by calling switchHoldingAndActive()
746                    if (ringingCall.isRinging()) {
747                        if (VDBG) log("CHLD:2 Callwaiting Answer call");
748                        PhoneUtils.answerCall(ringingCall);
749                        PhoneUtils.setMute(false);
750                        // Setting the second callers state flag to TRUE (i.e. active)
751                        cdmaSetSecondCallState(true);
752                        return true;
753                    } else if (PhoneGlobals.getInstance().cdmaPhoneCallState
754                               .getCurrentCallState()
755                               == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
756                        if (VDBG) log("CHLD:2 Swap Calls");
757                        PhoneUtils.switchHoldingAndActive(backgroundCall);
758                        // Toggle the second callers active state flag
759                        cdmaSwapSecondCallState();
760                        return true;
761                    }
762                    Log.e(TAG, "CDMA fail to do hold active and accept held");
763                    return false;
764                } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
765                    PhoneUtils.switchHoldingAndActive(backgroundCall);
766                    return true;
767                } else {
768                    Log.e(TAG, "Unexpected phone type: " + phoneType);
769                    return false;
770                }
771            } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
772                if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
773                    CdmaPhoneCallState.PhoneCallState state =
774                        PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState();
775                    // For CDMA, we need to check if the call is in THRWAY_ACTIVE state
776                    if (state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
777                        if (VDBG) log("CHLD:3 Merge Calls");
778                        PhoneUtils.mergeCalls();
779                        return true;
780                    }   else if (state == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
781                        // State is CONF_CALL already and we are getting a merge call
782                        // This can happen when CONF_CALL was entered from a Call Waiting
783                        // TODO(BT)
784                        return false;
785                    }
786                    Log.e(TAG, "GSG no call to add conference");
787                    return false;
788                } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
789                    if (mCM.hasActiveFgCall() && mCM.hasActiveBgCall()) {
790                        PhoneUtils.mergeCalls();
791                        return true;
792                    } else {
793                        Log.e(TAG, "GSG no call to merge");
794                        return false;
795                    }
796                } else {
797                    Log.e(TAG, "Unexpected phone type: " + phoneType);
798                    return false;
799                }
800            } else {
801                Log.e(TAG, "bad CHLD value: " + chld);
802                return false;
803            }
804        }
805
806        public String getNetworkOperator() {
807            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
808            return mCM.getDefaultPhone().getServiceState().getOperatorAlphaLong();
809        }
810
811        public String getSubscriberNumber() {
812            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
813            return mCM.getDefaultPhone().getLine1Number();
814        }
815
816        public boolean listCurrentCalls() {
817            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
818            Message msg = Message.obtain(mHandler, LIST_CURRENT_CALLS);
819            mHandler.sendMessage(msg);
820            return true;
821        }
822
823        public boolean queryPhoneState() {
824            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
825            Message msg = Message.obtain(mHandler, QUERY_PHONE_STATE);
826            mHandler.sendMessage(msg);
827            return true;
828        }
829
830        public void updateBtHandsfreeAfterRadioTechnologyChange() {
831            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
832            if (VDBG) Log.d(TAG, "updateBtHandsfreeAfterRadioTechnologyChange...");
833            updateBtPhoneStateAfterRadioTechnologyChange();
834        }
835
836        public void cdmaSwapSecondCallState() {
837            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
838            Message msg = Message.obtain(mHandler, CDMA_SWAP_SECOND_CALL_STATE);
839            mHandler.sendMessage(msg);
840        }
841
842        public void cdmaSetSecondCallState(boolean state) {
843            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
844            Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, state);
845            mHandler.sendMessage(msg);
846        }
847    };
848
849    // match up with bthf_call_state_t of bt_hf.h
850    final static int CALL_STATE_ACTIVE = 0;
851    final static int CALL_STATE_HELD = 1;
852    final static int CALL_STATE_DIALING = 2;
853    final static int CALL_STATE_ALERTING = 3;
854    final static int CALL_STATE_INCOMING = 4;
855    final static int CALL_STATE_WAITING = 5;
856    final static int CALL_STATE_IDLE = 6;
857
858    // match up with bthf_chld_type_t of bt_hf.h
859    final static int CHLD_TYPE_RELEASEHELD = 0;
860    final static int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
861    final static int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
862    final static int CHLD_TYPE_ADDHELDTOCONF = 3;
863
864     /* Convert telephony phone call state into hf hal call state */
865    static int convertCallState(Call.State ringingState, Call.State foregroundState) {
866        int retval = CALL_STATE_IDLE;
867
868        if ((ringingState == Call.State.INCOMING) ||
869            (ringingState == Call.State.WAITING) )
870            retval = CALL_STATE_INCOMING;
871        else if (foregroundState == Call.State.DIALING)
872            retval = CALL_STATE_DIALING;
873        else if (foregroundState == Call.State.ALERTING)
874            retval = CALL_STATE_ALERTING;
875        else
876            retval = CALL_STATE_IDLE;
877
878        if (VDBG) {
879            Log.v(TAG, "Call state Converted2: " + ringingState + "/" + foregroundState + " -> " +
880                    retval);
881        }
882        return retval;
883    }
884
885    static int convertCallState(Call.State callState) {
886        int retval = CALL_STATE_IDLE;
887
888        switch (callState) {
889        case IDLE:
890        case DISCONNECTED:
891        case DISCONNECTING:
892            retval = CALL_STATE_IDLE;
893            break;
894        case ACTIVE:
895            retval = CALL_STATE_ACTIVE;
896            break;
897        case HOLDING:
898            retval = CALL_STATE_HELD;
899            break;
900        case DIALING:
901            retval = CALL_STATE_DIALING;
902            break;
903        case ALERTING:
904            retval = CALL_STATE_ALERTING;
905            break;
906        case INCOMING:
907            retval = CALL_STATE_INCOMING;
908            break;
909        case WAITING:
910            retval = CALL_STATE_WAITING;
911            break;
912        default:
913            Log.e(TAG, "bad call state: " + callState);
914            retval = CALL_STATE_IDLE;
915            break;
916        }
917
918        if (VDBG) {
919            Log.v(TAG, "Call state Converted2: " + callState + " -> " + retval);
920        }
921
922        return retval;
923    }
924
925    private static void log(String msg) {
926        Log.d(TAG, msg);
927    }
928}
929