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