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