1 /* 2 * Copyright (C) 2014 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.server.telecom; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothHeadset; 21 import android.bluetooth.BluetoothProfile; 22 import android.bluetooth.IBluetoothHeadsetPhone; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.net.Uri; 28 import android.os.Binder; 29 import android.os.IBinder; 30 import android.os.RemoteException; 31 import android.telecom.Connection; 32 import android.telecom.PhoneAccount; 33 import android.telephony.PhoneNumberUtils; 34 import android.telephony.TelephonyManager; 35 import android.text.TextUtils; 36 37 import com.android.server.telecom.CallsManager.CallsManagerListener; 38 39 import java.util.Collection; 40 import java.util.HashMap; 41 import java.util.List; 42 import java.util.Map; 43 44 /** 45 * Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device 46 * and accepts call-related commands to perform on behalf of the BT device. 47 */ 48 public final class BluetoothPhoneServiceImpl { 49 50 private static final String TAG = "BluetoothPhoneService"; 51 52 // match up with bthf_call_state_t of bt_hf.h 53 private static final int CALL_STATE_ACTIVE = 0; 54 private static final int CALL_STATE_HELD = 1; 55 private static final int CALL_STATE_DIALING = 2; 56 private static final int CALL_STATE_ALERTING = 3; 57 private static final int CALL_STATE_INCOMING = 4; 58 private static final int CALL_STATE_WAITING = 5; 59 private static final int CALL_STATE_IDLE = 6; 60 61 // match up with bthf_call_state_t of bt_hf.h 62 // Terminate all held or set UDUB("busy") to a waiting call 63 private static final int CHLD_TYPE_RELEASEHELD = 0; 64 // Terminate all active calls and accepts a waiting/held call 65 private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1; 66 // Hold all active calls and accepts a waiting/held call 67 private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2; 68 // Add all held calls to a conference 69 private static final int CHLD_TYPE_ADDHELDTOCONF = 3; 70 71 private int mNumActiveCalls = 0; 72 private int mNumHeldCalls = 0; 73 private int mBluetoothCallState = CALL_STATE_IDLE; 74 private String mRingingAddress = null; 75 private int mRingingAddressType = 0; 76 private Call mOldHeldCall = null; 77 78 /** 79 * Binder implementation of IBluetoothHeadsetPhone. Implements the command interface that the 80 * bluetooth headset code uses to control call. 81 */ 82 private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() { 83 @Override 84 public boolean answerCall() throws RemoteException { 85 synchronized (mLock) { 86 enforceModifyPermission(); 87 88 long token = Binder.clearCallingIdentity(); 89 try { 90 Log.i(TAG, "BT - answering call"); 91 Call call = mCallsManager.getRingingCall(); 92 if (call != null) { 93 mCallsManager.answerCall(call, call.getVideoState()); 94 return true; 95 } 96 return false; 97 } finally { 98 Binder.restoreCallingIdentity(token); 99 } 100 101 } 102 } 103 104 @Override 105 public boolean hangupCall() throws RemoteException { 106 synchronized (mLock) { 107 enforceModifyPermission(); 108 109 long token = Binder.clearCallingIdentity(); 110 try { 111 Log.i(TAG, "BT - hanging up call"); 112 Call call = mCallsManager.getForegroundCall(); 113 if (call != null) { 114 mCallsManager.disconnectCall(call); 115 return true; 116 } 117 return false; 118 } finally { 119 Binder.restoreCallingIdentity(token); 120 } 121 } 122 } 123 124 @Override 125 public boolean sendDtmf(int dtmf) throws RemoteException { 126 synchronized (mLock) { 127 enforceModifyPermission(); 128 129 long token = Binder.clearCallingIdentity(); 130 try { 131 Log.i(TAG, "BT - sendDtmf %c", Log.DEBUG ? dtmf : '.'); 132 Call call = mCallsManager.getForegroundCall(); 133 if (call != null) { 134 // TODO: Consider making this a queue instead of starting/stopping 135 // in quick succession. 136 mCallsManager.playDtmfTone(call, (char) dtmf); 137 mCallsManager.stopDtmfTone(call); 138 return true; 139 } 140 return false; 141 } finally { 142 Binder.restoreCallingIdentity(token); 143 } 144 } 145 } 146 147 @Override 148 public String getNetworkOperator() throws RemoteException { 149 synchronized (mLock) { 150 enforceModifyPermission(); 151 152 long token = Binder.clearCallingIdentity(); 153 try { 154 Log.i(TAG, "getNetworkOperator"); 155 PhoneAccount account = getBestPhoneAccount(); 156 if (account != null) { 157 return account.getLabel().toString(); 158 } else { 159 // Finally, just get the network name from telephony. 160 return TelephonyManager.from(mContext) 161 .getNetworkOperatorName(); 162 } 163 } finally { 164 Binder.restoreCallingIdentity(token); 165 } 166 } 167 } 168 169 @Override 170 public String getSubscriberNumber() throws RemoteException { 171 synchronized (mLock) { 172 enforceModifyPermission(); 173 174 long token = Binder.clearCallingIdentity(); 175 try { 176 Log.i(TAG, "getSubscriberNumber"); 177 String address = null; 178 PhoneAccount account = getBestPhoneAccount(); 179 if (account != null) { 180 Uri addressUri = account.getAddress(); 181 if (addressUri != null) { 182 address = addressUri.getSchemeSpecificPart(); 183 } 184 } 185 if (TextUtils.isEmpty(address)) { 186 address = TelephonyManager.from(mContext).getLine1Number(); 187 if (address == null) address = ""; 188 } 189 return address; 190 } finally { 191 Binder.restoreCallingIdentity(token); 192 } 193 } 194 } 195 196 @Override 197 public boolean listCurrentCalls() throws RemoteException { 198 synchronized (mLock) { 199 enforceModifyPermission(); 200 201 long token = Binder.clearCallingIdentity(); 202 try { 203 // only log if it is after we recently updated the headset state or else it can 204 // clog the android log since this can be queried every second. 205 boolean logQuery = mHeadsetUpdatedRecently; 206 mHeadsetUpdatedRecently = false; 207 208 if (logQuery) { 209 Log.i(TAG, "listcurrentCalls"); 210 } 211 212 sendListOfCalls(logQuery); 213 return true; 214 } finally { 215 Binder.restoreCallingIdentity(token); 216 } 217 } 218 } 219 220 @Override 221 public boolean queryPhoneState() throws RemoteException { 222 synchronized (mLock) { 223 enforceModifyPermission(); 224 225 long token = Binder.clearCallingIdentity(); 226 try { 227 Log.i(TAG, "queryPhoneState"); 228 updateHeadsetWithCallState(true /* force */); 229 return true; 230 } finally { 231 Binder.restoreCallingIdentity(token); 232 } 233 } 234 } 235 236 @Override 237 public boolean processChld(int chld) throws RemoteException { 238 synchronized (mLock) { 239 enforceModifyPermission(); 240 241 long token = Binder.clearCallingIdentity(); 242 try { 243 Log.i(TAG, "processChld %d", chld); 244 return BluetoothPhoneServiceImpl.this.processChld(chld); 245 } finally { 246 Binder.restoreCallingIdentity(token); 247 } 248 } 249 } 250 251 @Override 252 public void updateBtHandsfreeAfterRadioTechnologyChange() throws RemoteException { 253 Log.d(TAG, "RAT change - deprecated"); 254 // deprecated 255 } 256 257 @Override 258 public void cdmaSetSecondCallState(boolean state) throws RemoteException { 259 Log.d(TAG, "cdma 1 - deprecated"); 260 // deprecated 261 } 262 263 @Override 264 public void cdmaSwapSecondCallState() throws RemoteException { 265 Log.d(TAG, "cdma 2 - deprecated"); 266 // deprecated 267 } 268 }; 269 270 /** 271 * Listens to call changes from the CallsManager and calls into methods to update the bluetooth 272 * headset with the new states. 273 */ 274 private CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() { 275 @Override 276 public void onCallAdded(Call call) { 277 updateHeadsetWithCallState(false /* force */); 278 } 279 280 @Override 281 public void onCallRemoved(Call call) { 282 mClccIndexMap.remove(call); 283 updateHeadsetWithCallState(false /* force */); 284 } 285 286 @Override 287 public void onCallStateChanged(Call call, int oldState, int newState) { 288 // If a call is being put on hold because of a new connecting call, ignore the 289 // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing 290 // state atomically. 291 // When the call later transitions to DIALING/DISCONNECTED we will then send out the 292 // aggregated update. 293 if (oldState == CallState.ACTIVE && newState == CallState.ON_HOLD) { 294 for (Call otherCall : mCallsManager.getCalls()) { 295 if (otherCall.getState() == CallState.CONNECTING) { 296 return; 297 } 298 } 299 } 300 301 // To have an active call and another dialing at the same time is an invalid BT 302 // state. We can assume that the active call will be automatically held which will 303 // send another update at which point we will be in the right state. 304 if (mCallsManager.getActiveCall() != null 305 && oldState == CallState.CONNECTING && newState == CallState.DIALING) { 306 return; 307 } 308 updateHeadsetWithCallState(false /* force */); 309 } 310 311 @Override 312 public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) { 313 // The BluetoothPhoneService does not need to respond to changes in foreground calls, 314 // which are always accompanied by call state changes anyway. 315 } 316 317 @Override 318 public void onIsConferencedChanged(Call call) { 319 /* 320 * Filter certain onIsConferencedChanged callbacks. Unfortunately this needs to be done 321 * because conference change events are not atomic and multiple callbacks get fired 322 * when two calls are conferenced together. This confuses updateHeadsetWithCallState 323 * if it runs in the middle of two calls being conferenced and can cause spurious and 324 * incorrect headset state updates. One of the scenarios is described below for CDMA 325 * conference calls. 326 * 327 * 1) Call 1 and Call 2 are being merged into conference Call 3. 328 * 2) Call 1 has its parent set to Call 3, but Call 2 does not have a parent yet. 329 * 3) updateHeadsetWithCallState now thinks that there are two active calls (Call 2 and 330 * Call 3) when there is actually only one active call (Call 3). 331 */ 332 if (call.getParentCall() != null) { 333 // If this call is newly conferenced, ignore the callback. We only care about the 334 // one sent for the parent conference call. 335 Log.d(this, "Ignoring onIsConferenceChanged from child call with new parent"); 336 return; 337 } 338 if (call.getChildCalls().size() == 1) { 339 // If this is a parent call with only one child, ignore the callback as well since 340 // the minimum number of child calls to start a conference call is 2. We expect 341 // this to be called again when the parent call has another child call added. 342 Log.d(this, "Ignoring onIsConferenceChanged from parent with only one child call"); 343 return; 344 } 345 updateHeadsetWithCallState(false /* force */); 346 } 347 }; 348 349 /** 350 * Listens to connections and disconnections of bluetooth headsets. We need to save the current 351 * bluetooth headset so that we know where to send call updates. 352 */ 353 private BluetoothProfile.ServiceListener mProfileListener = 354 new BluetoothProfile.ServiceListener() { 355 @Override 356 public void onServiceConnected(int profile, BluetoothProfile proxy) { 357 synchronized (mLock) { 358 mBluetoothHeadset = (BluetoothHeadset) proxy; 359 } 360 } 361 362 @Override 363 public void onServiceDisconnected(int profile) { 364 synchronized (mLock) { 365 mBluetoothHeadset = null; 366 } 367 } 368 }; 369 370 /** 371 * Receives events for global state changes of the bluetooth adapter. 372 */ 373 private final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() { 374 @Override 375 public void onReceive(Context context, Intent intent) { 376 synchronized (mLock) { 377 int state = intent 378 .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 379 Log.d(TAG, "Bluetooth Adapter state: %d", state); 380 if (state == BluetoothAdapter.STATE_ON) { 381 try { 382 mBinder.queryPhoneState(); 383 } catch (RemoteException e) { 384 // Remote exception not expected 385 } 386 } 387 } 388 } 389 }; 390 391 private BluetoothAdapter mBluetoothAdapter; 392 private BluetoothHeadset mBluetoothHeadset; 393 394 // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls). 395 private Map<Call, Integer> mClccIndexMap = new HashMap<>(); 396 397 private boolean mHeadsetUpdatedRecently = false; 398 399 private final Context mContext; 400 private final TelecomSystem.SyncRoot mLock; 401 private final CallsManager mCallsManager; 402 private final PhoneAccountRegistrar mPhoneAccountRegistrar; 403 404 public IBinder getBinder() { 405 return mBinder; 406 } 407 408 public BluetoothPhoneServiceImpl( 409 Context context, 410 TelecomSystem.SyncRoot lock, 411 CallsManager callsManager, 412 PhoneAccountRegistrar phoneAccountRegistrar) { 413 Log.d(this, "onCreate"); 414 415 mContext = context; 416 mLock = lock; 417 mCallsManager = callsManager; 418 mPhoneAccountRegistrar = phoneAccountRegistrar; 419 420 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 421 if (mBluetoothAdapter == null) { 422 Log.d(this, "BluetoothPhoneService shutting down, no BT Adapter found."); 423 return; 424 } 425 mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET); 426 427 IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); 428 context.registerReceiver(mBluetoothAdapterReceiver, intentFilter); 429 430 mCallsManager.addListener(mCallsManagerListener); 431 updateHeadsetWithCallState(false /* force */); 432 } 433 434 private boolean processChld(int chld) { 435 Call activeCall = mCallsManager.getActiveCall(); 436 Call ringingCall = mCallsManager.getRingingCall(); 437 Call heldCall = mCallsManager.getHeldCall(); 438 439 // TODO: Keeping as Log.i for now. Move to Log.d after L release if BT proves stable. 440 Log.i(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall); 441 442 if (chld == CHLD_TYPE_RELEASEHELD) { 443 if (ringingCall != null) { 444 mCallsManager.rejectCall(ringingCall, false, null); 445 return true; 446 } else if (heldCall != null) { 447 mCallsManager.disconnectCall(heldCall); 448 return true; 449 } 450 } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) { 451 if (activeCall != null) { 452 mCallsManager.disconnectCall(activeCall); 453 if (ringingCall != null) { 454 mCallsManager.answerCall(ringingCall, ringingCall.getVideoState()); 455 } else if (heldCall != null) { 456 mCallsManager.unholdCall(heldCall); 457 } 458 return true; 459 } 460 } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) { 461 if (activeCall != null && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) { 462 activeCall.swapConference(); 463 Log.i(TAG, "CDMA calls in conference swapped, updating headset"); 464 updateHeadsetWithCallState(true /* force */); 465 return true; 466 } else if (ringingCall != null) { 467 mCallsManager.answerCall(ringingCall, ringingCall.getVideoState()); 468 return true; 469 } else if (heldCall != null) { 470 // CallsManager will hold any active calls when unhold() is called on a 471 // currently-held call. 472 mCallsManager.unholdCall(heldCall); 473 return true; 474 } else if (activeCall != null && activeCall.can(Connection.CAPABILITY_HOLD)) { 475 mCallsManager.holdCall(activeCall); 476 return true; 477 } 478 } else if (chld == CHLD_TYPE_ADDHELDTOCONF) { 479 if (activeCall != null) { 480 if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) { 481 activeCall.mergeConference(); 482 return true; 483 } else { 484 List<Call> conferenceable = activeCall.getConferenceableCalls(); 485 if (!conferenceable.isEmpty()) { 486 mCallsManager.conference(activeCall, conferenceable.get(0)); 487 return true; 488 } 489 } 490 } 491 } 492 return false; 493 } 494 495 private void enforceModifyPermission() { 496 mContext.enforceCallingOrSelfPermission( 497 android.Manifest.permission.MODIFY_PHONE_STATE, null); 498 } 499 500 private void sendListOfCalls(boolean shouldLog) { 501 Collection<Call> mCalls = mCallsManager.getCalls(); 502 for (Call call : mCalls) { 503 // We don't send the parent conference call to the bluetooth device. 504 // We do, however want to send conferences that have no children to the bluetooth 505 // device (e.g. IMS Conference). 506 if (!call.isConference() || 507 (call.isConference() && call 508 .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN))) { 509 sendClccForCall(call, shouldLog); 510 } 511 } 512 sendClccEndMarker(); 513 } 514 515 /** 516 * Sends a single clcc (C* List Current Calls) event for the specified call. 517 */ 518 private void sendClccForCall(Call call, boolean shouldLog) { 519 boolean isForeground = mCallsManager.getForegroundCall() == call; 520 int state = convertCallState(call.getState(), isForeground); 521 boolean isPartOfConference = false; 522 boolean isConferenceWithNoChildren = call.isConference() && call 523 .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN); 524 525 if (state == CALL_STATE_IDLE) { 526 return; 527 } 528 529 Call conferenceCall = call.getParentCall(); 530 if (conferenceCall != null) { 531 isPartOfConference = true; 532 533 // Run some alternative states for Conference-level merge/swap support. 534 // Basically, if call supports swapping or merging at the conference-level, then we need 535 // to expose the calls as having distinct states (ACTIVE vs CAPABILITY_HOLD) or the 536 // functionality won't show up on the bluetooth device. 537 538 // Before doing any special logic, ensure that we are dealing with an ACTIVE call and 539 // that the conference itself has a notion of the current "active" child call. 540 Call activeChild = conferenceCall.getConferenceLevelActiveCall(); 541 if (state == CALL_STATE_ACTIVE && activeChild != null) { 542 // Reevaluate state if we can MERGE or if we can SWAP without previously having 543 // MERGED. 544 boolean shouldReevaluateState = 545 conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) || 546 (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) && 547 !conferenceCall.wasConferencePreviouslyMerged()); 548 549 if (shouldReevaluateState) { 550 isPartOfConference = false; 551 if (call == activeChild) { 552 state = CALL_STATE_ACTIVE; 553 } else { 554 // At this point we know there is an "active" child and we know that it is 555 // not this call, so set it to HELD instead. 556 state = CALL_STATE_HELD; 557 } 558 } 559 } 560 } else if (isConferenceWithNoChildren) { 561 // Handle the special case of an IMS conference call without conference event package 562 // support. The call will be marked as a conference, but the conference will not have 563 // child calls where conference event packages are not used by the carrier. 564 isPartOfConference = true; 565 } 566 567 int index = getIndexForCall(call); 568 int direction = call.isIncoming() ? 1 : 0; 569 final Uri addressUri; 570 if (call.getGatewayInfo() != null) { 571 addressUri = call.getGatewayInfo().getOriginalAddress(); 572 } else { 573 addressUri = call.getHandle(); 574 } 575 String address = addressUri == null ? null : addressUri.getSchemeSpecificPart(); 576 int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address); 577 578 if (shouldLog) { 579 Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d", 580 index, direction, state, isPartOfConference, Log.piiHandle(address), 581 addressType); 582 } 583 584 if (mBluetoothHeadset != null) { 585 mBluetoothHeadset.clccResponse( 586 index, direction, state, 0, isPartOfConference, address, addressType); 587 } 588 } 589 590 private void sendClccEndMarker() { 591 // End marker is recognized with an index value of 0. All other parameters are ignored. 592 if (mBluetoothHeadset != null) { 593 mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0); 594 } 595 } 596 597 /** 598 * Returns the caches index for the specified call. If no such index exists, then an index is 599 * given (smallest number starting from 1 that isn't already taken). 600 */ 601 private int getIndexForCall(Call call) { 602 if (mClccIndexMap.containsKey(call)) { 603 return mClccIndexMap.get(call); 604 } 605 606 int i = 1; // Indexes for bluetooth clcc are 1-based. 607 while (mClccIndexMap.containsValue(i)) { 608 i++; 609 } 610 611 // NOTE: Indexes are removed in {@link #onCallRemoved}. 612 mClccIndexMap.put(call, i); 613 return i; 614 } 615 616 /** 617 * Sends an update of the current call state to the current Headset. 618 * 619 * @param force {@code true} if the headset state should be sent regardless if no changes to the 620 * state have occurred, {@code false} if the state should only be sent if the state has 621 * changed. 622 */ 623 private void updateHeadsetWithCallState(boolean force) { 624 CallsManager callsManager = mCallsManager; 625 Call activeCall = mCallsManager.getActiveCall(); 626 Call ringingCall = mCallsManager.getRingingCall(); 627 Call heldCall = mCallsManager.getHeldCall(); 628 629 int bluetoothCallState = getBluetoothCallStateForUpdate(); 630 631 String ringingAddress = null; 632 int ringingAddressType = 128; 633 if (ringingCall != null && ringingCall.getHandle() != null) { 634 ringingAddress = ringingCall.getHandle().getSchemeSpecificPart(); 635 if (ringingAddress != null) { 636 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress); 637 } 638 } 639 if (ringingAddress == null) { 640 ringingAddress = ""; 641 } 642 643 int numActiveCalls = activeCall == null ? 0 : 1; 644 int numHeldCalls = mCallsManager.getNumHeldCalls(); 645 // Intermediate state for GSM calls which are in the process of being swapped. 646 // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls 647 // are held? 648 boolean callsPendingSwitch = (numHeldCalls == 2); 649 650 // For conference calls which support swapping the active call within the conference 651 // (namely CDMA calls) we need to expose that as a held call in order for the BT device 652 // to show "swap" and "merge" functionality. 653 boolean ignoreHeldCallChange = false; 654 if (activeCall != null && activeCall.isConference() && 655 !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) { 656 if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) { 657 // Indicate that BT device should show SWAP command by indicating that there is a 658 // call on hold, but only if the conference wasn't previously merged. 659 numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1; 660 } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) { 661 numHeldCalls = 1; // Merge is available, so expose via numHeldCalls. 662 } 663 664 for (Call childCall : activeCall.getChildCalls()) { 665 // Held call has changed due to it being combined into a CDMA conference. Keep 666 // track of this and ignore any future update since it doesn't really count as 667 // a call change. 668 if (mOldHeldCall == childCall) { 669 ignoreHeldCallChange = true; 670 break; 671 } 672 } 673 } 674 675 if (mBluetoothHeadset != null && 676 (force || 677 (!callsPendingSwitch && 678 (numActiveCalls != mNumActiveCalls || 679 numHeldCalls != mNumHeldCalls || 680 bluetoothCallState != mBluetoothCallState || 681 !TextUtils.equals(ringingAddress, mRingingAddress) || 682 ringingAddressType != mRingingAddressType || 683 (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) { 684 685 // If the call is transitioning into the alerting state, send DIALING first. 686 // Some devices expect to see a DIALING state prior to seeing an ALERTING state 687 // so we need to send it first. 688 boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState && 689 bluetoothCallState == CALL_STATE_ALERTING; 690 691 mOldHeldCall = heldCall; 692 mNumActiveCalls = numActiveCalls; 693 mNumHeldCalls = numHeldCalls; 694 mBluetoothCallState = bluetoothCallState; 695 mRingingAddress = ringingAddress; 696 mRingingAddressType = ringingAddressType; 697 698 if (sendDialingFirst) { 699 // Log in full to make logs easier to debug. 700 Log.i(TAG, "updateHeadsetWithCallState " + 701 "numActive %s, " + 702 "numHeld %s, " + 703 "callState %s, " + 704 "ringing number %s, " + 705 "ringing type %s", 706 mNumActiveCalls, 707 mNumHeldCalls, 708 CALL_STATE_DIALING, 709 Log.pii(mRingingAddress), 710 mRingingAddressType); 711 mBluetoothHeadset.phoneStateChanged( 712 mNumActiveCalls, 713 mNumHeldCalls, 714 CALL_STATE_DIALING, 715 mRingingAddress, 716 mRingingAddressType); 717 } 718 719 Log.i(TAG, "updateHeadsetWithCallState " + 720 "numActive %s, " + 721 "numHeld %s, " + 722 "callState %s, " + 723 "ringing number %s, " + 724 "ringing type %s", 725 mNumActiveCalls, 726 mNumHeldCalls, 727 mBluetoothCallState, 728 Log.pii(mRingingAddress), 729 mRingingAddressType); 730 731 mBluetoothHeadset.phoneStateChanged( 732 mNumActiveCalls, 733 mNumHeldCalls, 734 mBluetoothCallState, 735 mRingingAddress, 736 mRingingAddressType); 737 738 mHeadsetUpdatedRecently = true; 739 } 740 } 741 742 private int getBluetoothCallStateForUpdate() { 743 CallsManager callsManager = mCallsManager; 744 Call ringingCall = mCallsManager.getRingingCall(); 745 Call dialingCall = mCallsManager.getOutgoingCall(); 746 747 // 748 // !! WARNING !! 749 // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not 750 // used in this version of the call state mappings. This is on purpose. 751 // phone_state_change() in btif_hf.c is not written to handle these states. Only with the 752 // listCalls*() method are WAITING and ACTIVE used. 753 // Using the unsupported states here caused problems with inconsistent state in some 754 // bluetooth devices (like not getting out of ringing state after answering a call). 755 // 756 int bluetoothCallState = CALL_STATE_IDLE; 757 if (ringingCall != null) { 758 bluetoothCallState = CALL_STATE_INCOMING; 759 } else if (dialingCall != null) { 760 bluetoothCallState = CALL_STATE_ALERTING; 761 } 762 return bluetoothCallState; 763 } 764 765 private int convertCallState(int callState, boolean isForegroundCall) { 766 switch (callState) { 767 case CallState.NEW: 768 case CallState.ABORTED: 769 case CallState.DISCONNECTED: 770 case CallState.CONNECTING: 771 case CallState.SELECT_PHONE_ACCOUNT: 772 return CALL_STATE_IDLE; 773 774 case CallState.ACTIVE: 775 return CALL_STATE_ACTIVE; 776 777 case CallState.DIALING: 778 // Yes, this is correctly returning ALERTING. 779 // "Dialing" for BT means that we have sent information to the service provider 780 // to place the call but there is no confirmation that the call is going through. 781 // When there finally is confirmation, the ringback is played which is referred to 782 // as an "alert" tone, thus, ALERTING. 783 // TODO: We should consider using the ALERTING terms in Telecom because that 784 // seems to be more industry-standard. 785 return CALL_STATE_ALERTING; 786 787 case CallState.ON_HOLD: 788 return CALL_STATE_HELD; 789 790 case CallState.RINGING: 791 if (isForegroundCall) { 792 return CALL_STATE_INCOMING; 793 } else { 794 return CALL_STATE_WAITING; 795 } 796 } 797 return CALL_STATE_IDLE; 798 } 799 800 /** 801 * Returns the best phone account to use for the given state of all calls. 802 * First, tries to return the phone account for the foreground call, second the default 803 * phone account for PhoneAccount.SCHEME_TEL. 804 */ 805 private PhoneAccount getBestPhoneAccount() { 806 if (mPhoneAccountRegistrar == null) { 807 return null; 808 } 809 810 Call call = mCallsManager.getForegroundCall(); 811 812 PhoneAccount account = null; 813 if (call != null) { 814 // First try to get the network name of the foreground call. 815 account = mPhoneAccountRegistrar.getPhoneAccountCheckCallingUser( 816 call.getTargetPhoneAccount()); 817 } 818 819 if (account == null) { 820 // Second, Try to get the label for the default Phone Account. 821 account = mPhoneAccountRegistrar.getPhoneAccountCheckCallingUser( 822 mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme( 823 PhoneAccount.SCHEME_TEL)); 824 } 825 return account; 826 } 827 } 828