BluetoothPhoneServiceImpl.java revision e091ab90e37845cf4771051a6d2ce0ebadee4fe7
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) { 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 BluetoothAdapter 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 PhoneAccountRegistrar phoneAccountRegistrar) { 426 Log.d(this, "onCreate"); 427 428 mContext = context; 429 mLock = lock; 430 mCallsManager = callsManager; 431 mPhoneAccountRegistrar = phoneAccountRegistrar; 432 433 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 434 if (mBluetoothAdapter == null) { 435 Log.d(this, "BluetoothPhoneService shutting down, no BT Adapter found."); 436 return; 437 } 438 mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET); 439 440 IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); 441 context.registerReceiver(mBluetoothAdapterReceiver, intentFilter); 442 443 mCallsManager.addListener(mCallsManagerListener); 444 updateHeadsetWithCallState(false /* force */); 445 } 446 447 @VisibleForTesting 448 public void setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset) { 449 mBluetoothHeadset = bluetoothHeadset; 450 } 451 452 private boolean processChld(int chld) { 453 Call activeCall = mCallsManager.getActiveCall(); 454 Call ringingCall = mCallsManager.getRingingCall(); 455 Call heldCall = mCallsManager.getHeldCall(); 456 457 // TODO: Keeping as Log.i for now. Move to Log.d after L release if BT proves stable. 458 Log.i(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall); 459 460 if (chld == CHLD_TYPE_RELEASEHELD) { 461 if (ringingCall != null) { 462 mCallsManager.rejectCall(ringingCall, false, null); 463 return true; 464 } else if (heldCall != null) { 465 mCallsManager.disconnectCall(heldCall); 466 return true; 467 } 468 } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) { 469 if (activeCall != null) { 470 mCallsManager.disconnectCall(activeCall); 471 if (ringingCall != null) { 472 mCallsManager.answerCall(ringingCall, ringingCall.getVideoState()); 473 } else if (heldCall != null) { 474 mCallsManager.unholdCall(heldCall); 475 } 476 return true; 477 } 478 } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) { 479 if (activeCall != null && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) { 480 activeCall.swapConference(); 481 Log.i(TAG, "CDMA calls in conference swapped, updating headset"); 482 updateHeadsetWithCallState(true /* force */); 483 return true; 484 } else if (ringingCall != null) { 485 mCallsManager.answerCall(ringingCall, ringingCall.getVideoState()); 486 return true; 487 } else if (heldCall != null) { 488 // CallsManager will hold any active calls when unhold() is called on a 489 // currently-held call. 490 mCallsManager.unholdCall(heldCall); 491 return true; 492 } else if (activeCall != null && activeCall.can(Connection.CAPABILITY_HOLD)) { 493 mCallsManager.holdCall(activeCall); 494 return true; 495 } 496 } else if (chld == CHLD_TYPE_ADDHELDTOCONF) { 497 if (activeCall != null) { 498 if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) { 499 activeCall.mergeConference(); 500 return true; 501 } else { 502 List<Call> conferenceable = activeCall.getConferenceableCalls(); 503 if (!conferenceable.isEmpty()) { 504 mCallsManager.conference(activeCall, conferenceable.get(0)); 505 return true; 506 } 507 } 508 } 509 } 510 return false; 511 } 512 513 private void enforceModifyPermission() { 514 mContext.enforceCallingOrSelfPermission( 515 android.Manifest.permission.MODIFY_PHONE_STATE, null); 516 } 517 518 private void sendListOfCalls(boolean shouldLog) { 519 Collection<Call> mCalls = mCallsManager.getCalls(); 520 for (Call call : mCalls) { 521 // We don't send the parent conference call to the bluetooth device. 522 // We do, however want to send conferences that have no children to the bluetooth 523 // device (e.g. IMS Conference). 524 if (!call.isConference() || 525 (call.isConference() && call 526 .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN))) { 527 sendClccForCall(call, shouldLog); 528 } 529 } 530 sendClccEndMarker(); 531 } 532 533 /** 534 * Sends a single clcc (C* List Current Calls) event for the specified call. 535 */ 536 private void sendClccForCall(Call call, boolean shouldLog) { 537 boolean isForeground = mCallsManager.getForegroundCall() == call; 538 int state = convertCallState(call.getState(), isForeground); 539 boolean isPartOfConference = false; 540 boolean isConferenceWithNoChildren = call.isConference() && call 541 .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN); 542 543 if (state == CALL_STATE_IDLE) { 544 return; 545 } 546 547 Call conferenceCall = call.getParentCall(); 548 if (conferenceCall != null) { 549 isPartOfConference = true; 550 551 // Run some alternative states for Conference-level merge/swap support. 552 // Basically, if call supports swapping or merging at the conference-level, then we need 553 // to expose the calls as having distinct states (ACTIVE vs CAPABILITY_HOLD) or the 554 // functionality won't show up on the bluetooth device. 555 556 // Before doing any special logic, ensure that we are dealing with an ACTIVE call and 557 // that the conference itself has a notion of the current "active" child call. 558 Call activeChild = conferenceCall.getConferenceLevelActiveCall(); 559 if (state == CALL_STATE_ACTIVE && activeChild != null) { 560 // Reevaluate state if we can MERGE or if we can SWAP without previously having 561 // MERGED. 562 boolean shouldReevaluateState = 563 conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) || 564 (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) && 565 !conferenceCall.wasConferencePreviouslyMerged()); 566 567 if (shouldReevaluateState) { 568 isPartOfConference = false; 569 if (call == activeChild) { 570 state = CALL_STATE_ACTIVE; 571 } else { 572 // At this point we know there is an "active" child and we know that it is 573 // not this call, so set it to HELD instead. 574 state = CALL_STATE_HELD; 575 } 576 } 577 } 578 } else if (isConferenceWithNoChildren) { 579 // Handle the special case of an IMS conference call without conference event package 580 // support. The call will be marked as a conference, but the conference will not have 581 // child calls where conference event packages are not used by the carrier. 582 isPartOfConference = true; 583 } 584 585 int index = getIndexForCall(call); 586 int direction = call.isIncoming() ? 1 : 0; 587 final Uri addressUri; 588 if (call.getGatewayInfo() != null) { 589 addressUri = call.getGatewayInfo().getOriginalAddress(); 590 } else { 591 addressUri = call.getHandle(); 592 } 593 String address = addressUri == null ? null : addressUri.getSchemeSpecificPart(); 594 int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address); 595 596 if (shouldLog) { 597 Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d", 598 index, direction, state, isPartOfConference, Log.piiHandle(address), 599 addressType); 600 } 601 602 if (mBluetoothHeadset != null) { 603 mBluetoothHeadset.clccResponse( 604 index, direction, state, 0, isPartOfConference, address, addressType); 605 } 606 } 607 608 private void sendClccEndMarker() { 609 // End marker is recognized with an index value of 0. All other parameters are ignored. 610 if (mBluetoothHeadset != null) { 611 mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0); 612 } 613 } 614 615 /** 616 * Returns the caches index for the specified call. If no such index exists, then an index is 617 * given (smallest number starting from 1 that isn't already taken). 618 */ 619 private int getIndexForCall(Call call) { 620 if (mClccIndexMap.containsKey(call)) { 621 return mClccIndexMap.get(call); 622 } 623 624 int i = 1; // Indexes for bluetooth clcc are 1-based. 625 while (mClccIndexMap.containsValue(i)) { 626 i++; 627 } 628 629 // NOTE: Indexes are removed in {@link #onCallRemoved}. 630 mClccIndexMap.put(call, i); 631 return i; 632 } 633 634 /** 635 * Sends an update of the current call state to the current Headset. 636 * 637 * @param force {@code true} if the headset state should be sent regardless if no changes to the 638 * state have occurred, {@code false} if the state should only be sent if the state has 639 * changed. 640 */ 641 private void updateHeadsetWithCallState(boolean force) { 642 Call activeCall = mCallsManager.getActiveCall(); 643 Call ringingCall = mCallsManager.getRingingCall(); 644 Call heldCall = mCallsManager.getHeldCall(); 645 646 int bluetoothCallState = getBluetoothCallStateForUpdate(); 647 648 String ringingAddress = null; 649 int ringingAddressType = 128; 650 if (ringingCall != null && ringingCall.getHandle() != null) { 651 ringingAddress = ringingCall.getHandle().getSchemeSpecificPart(); 652 if (ringingAddress != null) { 653 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress); 654 } 655 } 656 if (ringingAddress == null) { 657 ringingAddress = ""; 658 } 659 660 int numActiveCalls = activeCall == null ? 0 : 1; 661 int numHeldCalls = mCallsManager.getNumHeldCalls(); 662 // Intermediate state for GSM calls which are in the process of being swapped. 663 // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls 664 // are held? 665 boolean callsPendingSwitch = (numHeldCalls == 2); 666 667 // For conference calls which support swapping the active call within the conference 668 // (namely CDMA calls) we need to expose that as a held call in order for the BT device 669 // to show "swap" and "merge" functionality. 670 boolean ignoreHeldCallChange = false; 671 if (activeCall != null && activeCall.isConference() && 672 !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) { 673 if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) { 674 // Indicate that BT device should show SWAP command by indicating that there is a 675 // call on hold, but only if the conference wasn't previously merged. 676 numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1; 677 } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) { 678 numHeldCalls = 1; // Merge is available, so expose via numHeldCalls. 679 } 680 681 for (Call childCall : activeCall.getChildCalls()) { 682 // Held call has changed due to it being combined into a CDMA conference. Keep 683 // track of this and ignore any future update since it doesn't really count as 684 // a call change. 685 if (mOldHeldCall == childCall) { 686 ignoreHeldCallChange = true; 687 break; 688 } 689 } 690 } 691 692 if (mBluetoothHeadset != null && 693 (force || 694 (!callsPendingSwitch && 695 (numActiveCalls != mNumActiveCalls || 696 numHeldCalls != mNumHeldCalls || 697 bluetoothCallState != mBluetoothCallState || 698 !TextUtils.equals(ringingAddress, mRingingAddress) || 699 ringingAddressType != mRingingAddressType || 700 (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) { 701 702 // If the call is transitioning into the alerting state, send DIALING first. 703 // Some devices expect to see a DIALING state prior to seeing an ALERTING state 704 // so we need to send it first. 705 boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState && 706 bluetoothCallState == CALL_STATE_ALERTING; 707 708 mOldHeldCall = heldCall; 709 mNumActiveCalls = numActiveCalls; 710 mNumHeldCalls = numHeldCalls; 711 mBluetoothCallState = bluetoothCallState; 712 mRingingAddress = ringingAddress; 713 mRingingAddressType = ringingAddressType; 714 715 if (sendDialingFirst) { 716 // Log in full to make logs easier to debug. 717 Log.i(TAG, "updateHeadsetWithCallState " + 718 "numActive %s, " + 719 "numHeld %s, " + 720 "callState %s, " + 721 "ringing number %s, " + 722 "ringing type %s", 723 mNumActiveCalls, 724 mNumHeldCalls, 725 CALL_STATE_DIALING, 726 Log.pii(mRingingAddress), 727 mRingingAddressType); 728 mBluetoothHeadset.phoneStateChanged( 729 mNumActiveCalls, 730 mNumHeldCalls, 731 CALL_STATE_DIALING, 732 mRingingAddress, 733 mRingingAddressType); 734 } 735 736 Log.i(TAG, "updateHeadsetWithCallState " + 737 "numActive %s, " + 738 "numHeld %s, " + 739 "callState %s, " + 740 "ringing number %s, " + 741 "ringing type %s", 742 mNumActiveCalls, 743 mNumHeldCalls, 744 mBluetoothCallState, 745 Log.pii(mRingingAddress), 746 mRingingAddressType); 747 748 mBluetoothHeadset.phoneStateChanged( 749 mNumActiveCalls, 750 mNumHeldCalls, 751 mBluetoothCallState, 752 mRingingAddress, 753 mRingingAddressType); 754 755 mHeadsetUpdatedRecently = true; 756 } 757 } 758 759 private int getBluetoothCallStateForUpdate() { 760 CallsManager callsManager = mCallsManager; 761 Call ringingCall = mCallsManager.getRingingCall(); 762 Call dialingCall = mCallsManager.getOutgoingCall(); 763 764 // 765 // !! WARNING !! 766 // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not 767 // used in this version of the call state mappings. This is on purpose. 768 // phone_state_change() in btif_hf.c is not written to handle these states. Only with the 769 // listCalls*() method are WAITING and ACTIVE used. 770 // Using the unsupported states here caused problems with inconsistent state in some 771 // bluetooth devices (like not getting out of ringing state after answering a call). 772 // 773 int bluetoothCallState = CALL_STATE_IDLE; 774 if (ringingCall != null) { 775 bluetoothCallState = CALL_STATE_INCOMING; 776 } else if (dialingCall != null) { 777 bluetoothCallState = CALL_STATE_ALERTING; 778 } 779 return bluetoothCallState; 780 } 781 782 private int convertCallState(int callState, boolean isForegroundCall) { 783 switch (callState) { 784 case CallState.NEW: 785 case CallState.ABORTED: 786 case CallState.DISCONNECTED: 787 return CALL_STATE_IDLE; 788 789 case CallState.ACTIVE: 790 return CALL_STATE_ACTIVE; 791 792 case CallState.CONNECTING: 793 case CallState.SELECT_PHONE_ACCOUNT: 794 case CallState.DIALING: 795 // Yes, this is correctly returning ALERTING. 796 // "Dialing" for BT means that we have sent information to the service provider 797 // to place the call but there is no confirmation that the call is going through. 798 // When there finally is confirmation, the ringback is played which is referred to 799 // as an "alert" tone, thus, ALERTING. 800 // TODO: We should consider using the ALERTING terms in Telecom because that 801 // seems to be more industry-standard. 802 return CALL_STATE_ALERTING; 803 804 case CallState.ON_HOLD: 805 return CALL_STATE_HELD; 806 807 case CallState.RINGING: 808 if (isForegroundCall) { 809 return CALL_STATE_INCOMING; 810 } else { 811 return CALL_STATE_WAITING; 812 } 813 } 814 return CALL_STATE_IDLE; 815 } 816 817 /** 818 * Returns the best phone account to use for the given state of all calls. 819 * First, tries to return the phone account for the foreground call, second the default 820 * phone account for PhoneAccount.SCHEME_TEL. 821 */ 822 private PhoneAccount getBestPhoneAccount() { 823 if (mPhoneAccountRegistrar == null) { 824 return null; 825 } 826 827 Call call = mCallsManager.getForegroundCall(); 828 829 PhoneAccount account = null; 830 if (call != null) { 831 // First try to get the network name of the foreground call. 832 account = mPhoneAccountRegistrar.getPhoneAccountOfCurrentUser( 833 call.getTargetPhoneAccount()); 834 } 835 836 if (account == null) { 837 // Second, Try to get the label for the default Phone Account. 838 account = mPhoneAccountRegistrar.getPhoneAccountUnchecked( 839 mPhoneAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser( 840 PhoneAccount.SCHEME_TEL)); 841 } 842 return account; 843 } 844} 845