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