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