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