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