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