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