• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.phone;
18 
19 import android.app.Service;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothHeadset;
23 import android.bluetooth.BluetoothProfile;
24 import android.bluetooth.IBluetoothHeadsetPhone;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.os.AsyncResult;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.os.Message;
31 import android.os.PowerManager;
32 import android.os.PowerManager.WakeLock;
33 import android.os.SystemProperties;
34 import android.telephony.PhoneNumberUtils;
35 import android.telephony.ServiceState;
36 import android.util.Log;
37 
38 import com.android.internal.telephony.Call;
39 import com.android.internal.telephony.Connection;
40 import com.android.internal.telephony.Phone;
41 import com.android.internal.telephony.PhoneConstants;
42 import com.android.internal.telephony.TelephonyIntents;
43 import com.android.internal.telephony.CallManager;
44 
45 import java.io.IOException;
46 import java.util.LinkedList;
47 import java.util.List;
48 
49 /**
50  * Bluetooth headset manager for the Phone app.
51  * @hide
52  */
53 public 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
onCreate()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
onStart(Intent intent, int startId)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
onDestroy()157     public void onDestroy() {
158         super.onDestroy();
159         if (DBG) log("Stopping Bluetooth BluetoothPhoneService Service");
160     }
161 
162     @Override
onBind(Intent intent)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 
updateBtPhoneStateAfterRadioTechnologyChange()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 
updateServiceState(ServiceState state)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 
handlePreciseCallStateChange(Connection connection)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 
handleListCurrentCalls()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 
handleQueryPhoneState()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 
getNumHeldUmts()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 
getNumHeldCdma()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 
getCallNumber(Connection connection, Call call)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 
CallNumber(String number, int type)440         private CallNumber(String number, int type) {
441             mNumber = number;
442             mType = type;
443         }
444 
equalTo(CallNumber callNumber)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 
listCurrentCallsGsm()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 */
sendClccResponseGsm(int index, Connection connection)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. */
listCurrentCallsCdma()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 */
sendClccResponseCdma(int index, Connection connection)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 
handleCdmaSwapSecondCallState()693     private void handleCdmaSwapSecondCallState() {
694         if (VDBG) log("cdmaSwapSecondCallState: Toggling mCdmaIsSecondCallActive");
695         mCdmaIsSecondCallActive = !mCdmaIsSecondCallActive;
696         mCdmaCallsSwapped = true;
697     }
698 
handleCdmaSetSecondCallState(boolean state)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 */
convertCallState(Call.State ringingState, Call.State foregroundState)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 
convertCallState(Call.State callState)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 
log(String msg)928     private static void log(String msg) {
929         Log.d(TAG, msg);
930     }
931 }
932