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