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