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