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