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