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 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 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 151 public void onDestroy() { 152 super.onDestroy(); 153 if (DBG) log("Stopping Bluetooth BluetoothPhoneService Service"); 154 } 155 156 @Override 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 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 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 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 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 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 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 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 419 private CallNumber(String number, int type) { 420 mNumber = number; 421 mType = type; 422 } 423 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 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 */ 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. */ 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 */ 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 694 private void handleCdmaSwapSecondCallState() { 695 if (VDBG) log("cdmaSwapSecondCallState: Toggling mCdmaIsSecondCallActive"); 696 mCdmaIsSecondCallActive = !mCdmaIsSecondCallActive; 697 mCdmaCallsSwapped = true; 698 } 699 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 */ 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 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 953 private static void log(String msg) { 954 Log.d(TAG, msg); 955 } 956 } 957