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