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