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