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