1 /* 2 * Copyright (C) 2020 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.bluetooth.telephony; 18 19 import android.annotation.RequiresPermission; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothHeadset; 22 import android.bluetooth.BluetoothProfile; 23 import android.content.BroadcastReceiver; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.pm.PackageManager; 29 import android.net.Uri; 30 import android.os.Binder; 31 import android.os.Bundle; 32 import android.os.IBinder; 33 import android.telecom.BluetoothCallQualityReport; 34 import android.telecom.Call; 35 import android.telecom.CallAudioState; 36 import android.telecom.Connection; 37 import android.telecom.InCallService; 38 import android.telecom.PhoneAccount; 39 import android.telecom.PhoneAccountHandle; 40 import android.telecom.TelecomManager; 41 import android.telecom.VideoProfile; 42 import android.telephony.PhoneNumberUtils; 43 import android.telephony.TelephonyManager; 44 import android.text.TextUtils; 45 import android.util.Log; 46 47 import com.android.bluetooth.hfp.BluetoothHeadsetProxy; 48 import com.android.bluetooth.hfp.HeadsetService; 49 50 import androidx.annotation.VisibleForTesting; 51 52 import java.util.ArrayList; 53 import java.util.Collection; 54 import java.util.HashMap; 55 import java.util.LinkedHashSet; 56 import java.util.List; 57 import java.util.Map; 58 59 /** 60 * Used to receive updates about calls from the Telecom component. This service is bound to Telecom 61 * while there exist calls which potentially require UI. This includes ringing (incoming), dialing 62 * (outgoing), and active calls. When the last BluetoothCall is disconnected, Telecom will unbind 63 * to the service triggering InCallActivity (via CallList) to finish soon after. 64 */ 65 public class BluetoothInCallService extends InCallService { 66 67 private static final String TAG = "BluetoothInCallService"; 68 69 // match up with bthf_call_state_t of bt_hf.h 70 private static final int CALL_STATE_ACTIVE = 0; 71 private static final int CALL_STATE_HELD = 1; 72 private static final int CALL_STATE_DIALING = 2; 73 private static final int CALL_STATE_ALERTING = 3; 74 private static final int CALL_STATE_INCOMING = 4; 75 private static final int CALL_STATE_WAITING = 5; 76 private static final int CALL_STATE_IDLE = 6; 77 private static final int CALL_STATE_DISCONNECTED = 7; 78 79 // match up with bthf_call_state_t of bt_hf.h 80 // Terminate all held or set UDUB("busy") to a waiting call 81 private static final int CHLD_TYPE_RELEASEHELD = 0; 82 // Terminate all active calls and accepts a waiting/held call 83 private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1; 84 // Hold all active calls and accepts a waiting/held call 85 private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2; 86 // Add all held calls to a conference 87 private static final int CHLD_TYPE_ADDHELDTOCONF = 3; 88 89 // Indicates that no BluetoothCall is ringing 90 private static final int DEFAULT_RINGING_ADDRESS_TYPE = 128; 91 92 private int mNumActiveCalls = 0; 93 private int mNumHeldCalls = 0; 94 private int mNumChildrenOfActiveCall = 0; 95 private int mBluetoothCallState = CALL_STATE_IDLE; 96 private String mRingingAddress = ""; 97 private int mRingingAddressType = DEFAULT_RINGING_ADDRESS_TYPE; 98 private BluetoothCall mOldHeldCall = null; 99 private boolean mHeadsetUpdatedRecently = false; 100 private boolean mIsDisconnectedTonePlaying = false; 101 102 private static final Object LOCK = new Object(); 103 private BluetoothHeadsetProxy mBluetoothHeadset; 104 105 @VisibleForTesting 106 public TelephonyManager mTelephonyManager; 107 108 @VisibleForTesting 109 public TelecomManager mTelecomManager; 110 111 @VisibleForTesting 112 public final HashMap<String, CallStateCallback> mCallbacks = new HashMap<>(); 113 114 @VisibleForTesting 115 public final HashMap<String, BluetoothCall> mBluetoothCallHashMap = new HashMap<>(); 116 117 // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls). 118 private final Map<BluetoothCall, Integer> mClccIndexMap = new HashMap<>(); 119 120 private static BluetoothInCallService sInstance; 121 122 public CallInfo mCallInfo = new CallInfo(); 123 124 /** 125 * Listens to connections and disconnections of bluetooth headsets. We need to save the current 126 * bluetooth headset so that we know where to send BluetoothCall updates. 127 */ 128 @VisibleForTesting 129 public BluetoothProfile.ServiceListener mProfileListener = 130 new BluetoothProfile.ServiceListener() { 131 @Override 132 public void onServiceConnected(int profile, BluetoothProfile proxy) { 133 synchronized (LOCK) { 134 setBluetoothHeadset(new BluetoothHeadsetProxy((BluetoothHeadset) proxy)); 135 updateHeadsetWithCallState(true /* force */); 136 } 137 } 138 139 @Override 140 public void onServiceDisconnected(int profile) { 141 synchronized (LOCK) { 142 setBluetoothHeadset(null); 143 } 144 } 145 }; 146 147 public class BluetoothAdapterReceiver extends BroadcastReceiver { 148 @Override onReceive(Context context, Intent intent)149 public void onReceive(Context context, Intent intent) { 150 synchronized (LOCK) { 151 if (intent.getAction() != BluetoothAdapter.ACTION_STATE_CHANGED) { 152 Log.w(TAG, "BluetoothAdapterReceiver: Intent action " + intent.getAction()); 153 return; 154 } 155 int state = intent 156 .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 157 Log.d(TAG, "Bluetooth Adapter state: " + state); 158 if (state == BluetoothAdapter.STATE_ON) { 159 queryPhoneState(); 160 } 161 } 162 } 163 }; 164 165 /** 166 * Receives events for global state changes of the bluetooth adapter. 167 */ 168 // TODO: The code is moved from Telecom stack. Since we're running in the BT process itself, 169 // we may be able to simplify this in a future patch. 170 @VisibleForTesting 171 public BluetoothAdapterReceiver mBluetoothAdapterReceiver; 172 173 @VisibleForTesting 174 public class CallStateCallback extends Call.Callback { 175 public int mLastState; 176 CallStateCallback(int initialState)177 public CallStateCallback(int initialState) { 178 mLastState = initialState; 179 } 180 getLastState()181 public int getLastState() { 182 return mLastState; 183 } 184 onStateChanged(BluetoothCall call, int state)185 public void onStateChanged(BluetoothCall call, int state) { 186 if (mCallInfo.isNullCall(call)) { 187 return; 188 } 189 if (call.isExternalCall()) { 190 return; 191 } 192 193 // If a BluetoothCall is being put on hold because of a new connecting call, ignore the 194 // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing 195 // state atomically. 196 // When the BluetoothCall later transitions to DIALING/DISCONNECTED we will then 197 // send out the aggregated update. 198 if (getLastState() == Call.STATE_ACTIVE && state == Call.STATE_HOLDING) { 199 for (BluetoothCall otherCall : mCallInfo.getBluetoothCalls()) { 200 if (otherCall.getState() == Call.STATE_CONNECTING) { 201 mLastState = state; 202 return; 203 } 204 } 205 } 206 207 // To have an active BluetoothCall and another dialing at the same time is an invalid BT 208 // state. We can assume that the active BluetoothCall will be automatically held 209 // which will send another update at which point we will be in the right state. 210 BluetoothCall activeCall = mCallInfo.getActiveCall(); 211 if (!mCallInfo.isNullCall(activeCall) 212 && getLastState() == Call.STATE_CONNECTING 213 && (state == Call.STATE_DIALING || state == Call.STATE_PULLING_CALL)) { 214 mLastState = state; 215 return; 216 } 217 mLastState = state; 218 updateHeadsetWithCallState(false /* force */); 219 } 220 221 @Override onStateChanged(Call call, int state)222 public void onStateChanged(Call call, int state) { 223 super.onStateChanged(call, state); 224 onStateChanged(getBluetoothCallById(call.getDetails().getTelecomCallId()), state); 225 } 226 onDetailsChanged(BluetoothCall call, Call.Details details)227 public void onDetailsChanged(BluetoothCall call, Call.Details details) { 228 if (mCallInfo.isNullCall(call)) { 229 return; 230 } 231 if (call.isExternalCall()) { 232 onCallRemoved(call); 233 } else { 234 onCallAdded(call); 235 } 236 } 237 238 @Override onDetailsChanged(Call call, Call.Details details)239 public void onDetailsChanged(Call call, Call.Details details) { 240 super.onDetailsChanged(call, details); 241 onDetailsChanged(getBluetoothCallById(call.getDetails().getTelecomCallId()), details); 242 } 243 onParentChanged(BluetoothCall call)244 public void onParentChanged(BluetoothCall call) { 245 if (call.isExternalCall()) { 246 return; 247 } 248 if (call.getParentId() != null) { 249 // If this BluetoothCall is newly conferenced, ignore the callback. 250 // We only care about the one sent for the parent conference call. 251 Log.d(TAG, 252 "Ignoring onIsConferenceChanged from child BluetoothCall with new parent"); 253 return; 254 } 255 updateHeadsetWithCallState(false /* force */); 256 } 257 258 @Override onParentChanged(Call call, Call parent)259 public void onParentChanged(Call call, Call parent) { 260 super.onParentChanged(call, parent); 261 onParentChanged( 262 getBluetoothCallById(call.getDetails().getTelecomCallId())); 263 } 264 onChildrenChanged(BluetoothCall call, List<BluetoothCall> children)265 public void onChildrenChanged(BluetoothCall call, List<BluetoothCall> children) { 266 if (call.isExternalCall()) { 267 return; 268 } 269 if (call.getChildrenIds().size() == 1) { 270 // If this is a parent BluetoothCall with only one child, 271 // ignore the callback as well since the minimum number of child calls to 272 // start a conference BluetoothCall is 2. We expect this to be called again 273 // when the parent BluetoothCall has another child BluetoothCall added. 274 Log.d(TAG, 275 "Ignoring onIsConferenceChanged from parent with only one child call"); 276 return; 277 } 278 updateHeadsetWithCallState(false /* force */); 279 } 280 281 @Override onChildrenChanged(Call call, List<Call> children)282 public void onChildrenChanged(Call call, List<Call> children) { 283 super.onChildrenChanged(call, children); 284 onChildrenChanged( 285 getBluetoothCallById(call.getDetails().getTelecomCallId()), 286 getBluetoothCallsByIds(BluetoothCall.getIds(children))); 287 } 288 } 289 290 @Override onBind(Intent intent)291 public IBinder onBind(Intent intent) { 292 Log.i(TAG, "onBind. Intent: " + intent); 293 BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 294 if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) { 295 Log.i(TAG, "Bluetooth is off"); 296 ComponentName componentName 297 = new ComponentName(getPackageName(), this.getClass().getName()); 298 getPackageManager().setComponentEnabledSetting( 299 componentName, 300 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 301 PackageManager.DONT_KILL_APP); 302 return null; 303 } 304 IBinder binder = super.onBind(intent); 305 mTelephonyManager = getSystemService(TelephonyManager.class); 306 mTelecomManager = getSystemService(TelecomManager.class); 307 return binder; 308 } 309 310 @Override onUnbind(Intent intent)311 public boolean onUnbind(Intent intent) { 312 Log.i(TAG, "onUnbind. Intent: " + intent); 313 return super.onUnbind(intent); 314 } 315 BluetoothInCallService()316 public BluetoothInCallService() { 317 Log.i(TAG, "BluetoothInCallService is created"); 318 sInstance = this; 319 } 320 getInstance()321 public static BluetoothInCallService getInstance() { 322 return sInstance; 323 } 324 325 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) enforceModifyPermission()326 protected void enforceModifyPermission() { 327 enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null); 328 } 329 330 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) answerCall()331 public boolean answerCall() { 332 synchronized (LOCK) { 333 enforceModifyPermission(); 334 Log.i(TAG, "BT - answering call"); 335 BluetoothCall call = mCallInfo.getRingingOrSimulatedRingingCall(); 336 if (mCallInfo.isNullCall(call)) { 337 return false; 338 } 339 call.answer(VideoProfile.STATE_AUDIO_ONLY); 340 return true; 341 } 342 } 343 344 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) hangupCall()345 public boolean hangupCall() { 346 synchronized (LOCK) { 347 enforceModifyPermission(); 348 Log.i(TAG, "BT - hanging up call"); 349 BluetoothCall call = mCallInfo.getForegroundCall(); 350 if (mCallInfo.isNullCall(call)) { 351 return false; 352 } 353 // release the parent if there is a conference call 354 BluetoothCall conferenceCall = getBluetoothCallById(call.getParentId()); 355 if (!mCallInfo.isNullCall(conferenceCall) 356 && conferenceCall.getState() == Call.STATE_ACTIVE) { 357 Log.i(TAG, "BT - hanging up conference call"); 358 call = conferenceCall; 359 } 360 if (call.getState() == Call.STATE_RINGING) { 361 call.reject(false, ""); 362 } else { 363 call.disconnect(); 364 } 365 return true; 366 } 367 } 368 369 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) sendDtmf(int dtmf)370 public boolean sendDtmf(int dtmf) { 371 synchronized (LOCK) { 372 enforceModifyPermission(); 373 Log.i(TAG, "BT - sendDtmf " + dtmf); 374 BluetoothCall call = mCallInfo.getForegroundCall(); 375 if (mCallInfo.isNullCall(call)) { 376 return false; 377 } 378 // TODO: Consider making this a queue instead of starting/stopping 379 // in quick succession. 380 call.playDtmfTone((char) dtmf); 381 call.stopDtmfTone(); 382 return true; 383 } 384 } 385 386 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) getNetworkOperator()387 public String getNetworkOperator() { 388 synchronized (LOCK) { 389 enforceModifyPermission(); 390 Log.i(TAG, "getNetworkOperator"); 391 PhoneAccount account = mCallInfo.getBestPhoneAccount(); 392 if (account != null && account.getLabel() != null) { 393 return account.getLabel().toString(); 394 } 395 // Finally, just get the network name from telephony. 396 return mTelephonyManager.getNetworkOperatorName(); 397 } 398 } 399 400 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) getSubscriberNumber()401 public String getSubscriberNumber() { 402 synchronized (LOCK) { 403 enforceModifyPermission(); 404 Log.i(TAG, "getSubscriberNumber"); 405 String address = null; 406 PhoneAccount account = mCallInfo.getBestPhoneAccount(); 407 if (account != null) { 408 Uri addressUri = account.getAddress(); 409 if (addressUri != null) { 410 address = addressUri.getSchemeSpecificPart(); 411 } 412 } 413 if (TextUtils.isEmpty(address)) { 414 address = mTelephonyManager.getLine1Number(); 415 if (address == null) address = ""; 416 } 417 return address; 418 } 419 } 420 421 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) listCurrentCalls()422 public boolean listCurrentCalls() { 423 synchronized (LOCK) { 424 enforceModifyPermission(); 425 // only log if it is after we recently updated the headset state or else it can 426 // clog the android log since this can be queried every second. 427 boolean logQuery = mHeadsetUpdatedRecently; 428 mHeadsetUpdatedRecently = false; 429 430 if (logQuery) { 431 Log.i(TAG, "listcurrentCalls"); 432 } 433 434 sendListOfCalls(logQuery); 435 return true; 436 } 437 } 438 439 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) queryPhoneState()440 public boolean queryPhoneState() { 441 synchronized (LOCK) { 442 enforceModifyPermission(); 443 Log.i(TAG, "queryPhoneState"); 444 updateHeadsetWithCallState(true); 445 return true; 446 } 447 } 448 449 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) processChld(int chld)450 public boolean processChld(int chld) { 451 synchronized (LOCK) { 452 enforceModifyPermission(); 453 long token = Binder.clearCallingIdentity(); 454 Log.i(TAG, "processChld " + chld); 455 return _processChld(chld); 456 } 457 } 458 onCallAdded(BluetoothCall call)459 public void onCallAdded(BluetoothCall call) { 460 if (call.isExternalCall()) { 461 return; 462 } 463 if (!mBluetoothCallHashMap.containsKey(call.getTelecomCallId())) { 464 Log.d(TAG, "onCallAdded"); 465 CallStateCallback callback = new CallStateCallback(call.getState()); 466 mCallbacks.put(call.getTelecomCallId(), callback); 467 call.registerCallback(callback); 468 469 mBluetoothCallHashMap.put(call.getTelecomCallId(), call); 470 updateHeadsetWithCallState(false /* force */); 471 } 472 } 473 sendBluetoothCallQualityReport( long timestamp, int rssi, int snr, int retransmissionCount, int packetsNotReceiveCount, int negativeAcknowledgementCount)474 public void sendBluetoothCallQualityReport( 475 long timestamp, 476 int rssi, 477 int snr, 478 int retransmissionCount, 479 int packetsNotReceiveCount, 480 int negativeAcknowledgementCount) { 481 BluetoothCall call = mCallInfo.getForegroundCall(); 482 if (mCallInfo.isNullCall(call)) { 483 Log.w(TAG, "No foreground call while trying to send BQR"); 484 return; 485 } 486 Bundle b = new Bundle(); 487 b.putParcelable( 488 BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT, 489 new BluetoothCallQualityReport.Builder() 490 .setSentTimestampMillis(timestamp) 491 .setChoppyVoice(true) 492 .setRssiDbm(rssi) 493 .setSnrDb(snr) 494 .setRetransmittedPacketsCount(retransmissionCount) 495 .setPacketsNotReceivedCount(packetsNotReceiveCount) 496 .setPacketsNotReceivedCount(negativeAcknowledgementCount) 497 .build()); 498 call.sendCallEvent( 499 BluetoothCallQualityReport.EVENT_BLUETOOTH_CALL_QUALITY_REPORT, b); 500 } 501 502 @Override onCallAdded(Call call)503 public void onCallAdded(Call call) { 504 super.onCallAdded(call); 505 onCallAdded(new BluetoothCall(call)); 506 } 507 onCallRemoved(BluetoothCall call)508 public void onCallRemoved(BluetoothCall call) { 509 if (call.isExternalCall()) { 510 return; 511 } 512 Log.d(TAG, "onCallRemoved"); 513 CallStateCallback callback = getCallback(call); 514 if (callback != null) { 515 call.unregisterCallback(callback); 516 } 517 518 if (mBluetoothCallHashMap.containsKey(call.getTelecomCallId())) { 519 mBluetoothCallHashMap.remove(call.getTelecomCallId()); 520 } 521 522 mClccIndexMap.remove(call); 523 updateHeadsetWithCallState(false /* force */); 524 } 525 526 @Override onCallRemoved(Call call)527 public void onCallRemoved(Call call) { 528 super.onCallRemoved(call); 529 BluetoothCall bluetoothCall = getBluetoothCallById(call.getDetails().getTelecomCallId()); 530 if (bluetoothCall == null) { 531 Log.w(TAG, "onCallRemoved, BluetoothCall is removed before registered"); 532 return; 533 } 534 onCallRemoved(bluetoothCall); 535 } 536 537 @Override onCallAudioStateChanged(CallAudioState audioState)538 public void onCallAudioStateChanged(CallAudioState audioState) { 539 super.onCallAudioStateChanged(audioState); 540 Log.d(TAG, "onCallAudioStateChanged, audioState == " + audioState); 541 } 542 543 544 @Override onCreate()545 public void onCreate() { 546 Log.d(TAG, "onCreate"); 547 super.onCreate(); 548 BluetoothAdapter.getDefaultAdapter() 549 .getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET); 550 mBluetoothAdapterReceiver = new BluetoothAdapterReceiver(); 551 IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); 552 registerReceiver(mBluetoothAdapterReceiver, intentFilter); 553 } 554 555 @Override onDestroy()556 public void onDestroy() { 557 Log.d(TAG, "onDestroy"); 558 if (mBluetoothAdapterReceiver != null) { 559 unregisterReceiver(mBluetoothAdapterReceiver); 560 mBluetoothAdapterReceiver = null; 561 } 562 super.onDestroy(); 563 } 564 sendListOfCalls(boolean shouldLog)565 private void sendListOfCalls(boolean shouldLog) { 566 Collection<BluetoothCall> calls = mCallInfo.getBluetoothCalls(); 567 for (BluetoothCall call : calls) { 568 // We don't send the parent conference BluetoothCall to the bluetooth device. 569 // We do, however want to send conferences that have no children to the bluetooth 570 // device (e.g. IMS Conference). 571 if (!call.isConference() 572 || (call.isConference() 573 && call.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN))) { 574 sendClccForCall(call, shouldLog); 575 } 576 } 577 sendClccEndMarker(); 578 } 579 sendClccEndMarker()580 private void sendClccEndMarker() { 581 // End marker is recognized with an index value of 0. All other parameters are ignored. 582 if (mBluetoothHeadset != null) { 583 mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0); 584 } 585 } 586 587 /** 588 * Sends a single clcc (C* List Current Calls) event for the specified call. 589 */ sendClccForCall(BluetoothCall call, boolean shouldLog)590 private void sendClccForCall(BluetoothCall call, boolean shouldLog) { 591 boolean isForeground = mCallInfo.getForegroundCall() == call; 592 int state = getBtCallState(call, isForeground); 593 boolean isPartOfConference = false; 594 boolean isConferenceWithNoChildren = call.isConference() 595 && call.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN); 596 597 if (state == CALL_STATE_IDLE) { 598 return; 599 } 600 601 BluetoothCall conferenceCall = getBluetoothCallById(call.getParentId()); 602 if (!mCallInfo.isNullCall(conferenceCall)) { 603 isPartOfConference = true; 604 605 // Run some alternative states for Conference-level merge/swap support. 606 // Basically, if BluetoothCall supports swapping or merging at the conference-level, 607 // then we need to expose the calls as having distinct states 608 // (ACTIVE vs CAPABILITY_HOLD) or 609 // the functionality won't show up on the bluetooth device. 610 611 // Before doing any special logic, ensure that we are dealing with an 612 // ACTIVE BluetoothCall and that the conference itself has a notion of 613 // the current "active" child call. 614 BluetoothCall activeChild = getBluetoothCallById( 615 conferenceCall.getGenericConferenceActiveChildCallId()); 616 if (state == CALL_STATE_ACTIVE && !mCallInfo.isNullCall(activeChild)) { 617 // Reevaluate state if we can MERGE or if we can SWAP without previously having 618 // MERGED. 619 boolean shouldReevaluateState = 620 conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) 621 || (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) 622 && !conferenceCall.wasConferencePreviouslyMerged()); 623 624 if (shouldReevaluateState) { 625 isPartOfConference = false; 626 if (call == activeChild) { 627 state = CALL_STATE_ACTIVE; 628 } else { 629 // At this point we know there is an "active" child and we know that it is 630 // not this call, so set it to HELD instead. 631 state = CALL_STATE_HELD; 632 } 633 } 634 } 635 if (conferenceCall.getState() == Call.STATE_HOLDING 636 && conferenceCall.can(Connection.CAPABILITY_MANAGE_CONFERENCE)) { 637 // If the parent IMS CEP conference BluetoothCall is on hold, we should mark 638 // this BluetoothCall as being on hold regardless of what the other 639 // children are doing. 640 state = CALL_STATE_HELD; 641 } 642 } else if (isConferenceWithNoChildren) { 643 // Handle the special case of an IMS conference BluetoothCall without conference 644 // event package support. 645 // The BluetoothCall will be marked as a conference, but the conference will not have 646 // child calls where conference event packages are not used by the carrier. 647 isPartOfConference = true; 648 } 649 650 int index = getIndexForCall(call); 651 int direction = call.isIncoming() ? 1 : 0; 652 final Uri addressUri; 653 if (call.getGatewayInfo() != null) { 654 addressUri = call.getGatewayInfo().getOriginalAddress(); 655 } else { 656 addressUri = call.getHandle(); 657 } 658 659 String address = addressUri == null ? null : addressUri.getSchemeSpecificPart(); 660 if (address != null) { 661 address = PhoneNumberUtils.stripSeparators(address); 662 } 663 664 int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address); 665 666 if (shouldLog) { 667 Log.i(TAG, "sending clcc for BluetoothCall " 668 + index + ", " 669 + direction + ", " 670 + state + ", " 671 + isPartOfConference + ", " 672 + addressType); 673 } 674 675 if (mBluetoothHeadset == null) { 676 Log.w(TAG, "mBluetoothHeasdset is null when sending clcc for BluetoothCall " 677 + index + ", " 678 + direction + ", " 679 + state + ", " 680 + isPartOfConference + ", " 681 + addressType); 682 } else { 683 mBluetoothHeadset.clccResponse( 684 index, direction, state, 0, isPartOfConference, address, addressType); 685 } 686 } 687 688 /** 689 * Returns the caches index for the specified call. If no such index exists, then an index is 690 * given (smallest number starting from 1 that isn't already taken). 691 */ getIndexForCall(BluetoothCall call)692 private int getIndexForCall(BluetoothCall call) { 693 if (mClccIndexMap.containsKey(call)) { 694 return mClccIndexMap.get(call); 695 } 696 697 int i = 1; // Indexes for bluetooth clcc are 1-based. 698 while (mClccIndexMap.containsValue(i)) { 699 i++; 700 } 701 702 // NOTE: Indexes are removed in {@link #onCallRemoved}. 703 mClccIndexMap.put(call, i); 704 return i; 705 } 706 _processChld(int chld)707 private boolean _processChld(int chld) { 708 BluetoothCall activeCall = mCallInfo.getActiveCall(); 709 BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall(); 710 if (ringingCall == null) { 711 Log.i(TAG, "asdf ringingCall null"); 712 } else { 713 Log.i(TAG, "asdf ringingCall not null " + ringingCall.hashCode()); 714 } 715 716 BluetoothCall heldCall = mCallInfo.getHeldCall(); 717 718 Log.i(TAG, "Active: " + activeCall 719 + " Ringing: " + ringingCall 720 + " Held: " + heldCall); 721 Log.i(TAG, "asdf chld " + chld); 722 723 if (chld == CHLD_TYPE_RELEASEHELD) { 724 Log.i(TAG, "asdf CHLD_TYPE_RELEASEHELD"); 725 if (!mCallInfo.isNullCall(ringingCall)) { 726 Log.i(TAG, "asdf reject " + ringingCall.hashCode()); 727 ringingCall.reject(false, null); 728 return true; 729 } else if (!mCallInfo.isNullCall(heldCall)) { 730 heldCall.disconnect(); 731 return true; 732 } 733 } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) { 734 if (mCallInfo.isNullCall(activeCall) 735 && mCallInfo.isNullCall(ringingCall) 736 && mCallInfo.isNullCall(heldCall)) { 737 return false; 738 } 739 if (!mCallInfo.isNullCall(activeCall)) { 740 BluetoothCall conferenceCall = getBluetoothCallById(activeCall.getParentId()); 741 if (!mCallInfo.isNullCall(conferenceCall) 742 && conferenceCall.getState() == Call.STATE_ACTIVE) { 743 Log.i(TAG, "CHLD: disconnect conference call"); 744 conferenceCall.disconnect(); 745 } else { 746 activeCall.disconnect(); 747 } 748 } 749 if (!mCallInfo.isNullCall(ringingCall)) { 750 ringingCall.answer(ringingCall.getVideoState()); 751 } else if (!mCallInfo.isNullCall(heldCall)) { 752 heldCall.unhold(); 753 } 754 return true; 755 } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) { 756 if (!mCallInfo.isNullCall(activeCall) 757 && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) { 758 activeCall.swapConference(); 759 Log.i(TAG, "CDMA calls in conference swapped, updating headset"); 760 updateHeadsetWithCallState(true /* force */); 761 return true; 762 } else if (!mCallInfo.isNullCall(ringingCall)) { 763 ringingCall.answer(VideoProfile.STATE_AUDIO_ONLY); 764 return true; 765 } else if (!mCallInfo.isNullCall(heldCall)) { 766 // CallsManager will hold any active calls when unhold() is called on a 767 // currently-held call. 768 heldCall.unhold(); 769 return true; 770 } else if (!mCallInfo.isNullCall(activeCall) 771 && activeCall.can(Connection.CAPABILITY_HOLD)) { 772 activeCall.hold(); 773 return true; 774 } 775 } else if (chld == CHLD_TYPE_ADDHELDTOCONF) { 776 if (!mCallInfo.isNullCall(activeCall)) { 777 if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) { 778 activeCall.mergeConference(); 779 return true; 780 } else { 781 List<BluetoothCall> conferenceable = getBluetoothCallsByIds( 782 activeCall.getConferenceableCalls()); 783 if (!conferenceable.isEmpty()) { 784 activeCall.conference(conferenceable.get(0)); 785 return true; 786 } 787 } 788 } 789 } 790 return false; 791 } 792 793 /** 794 * Sends an update of the current BluetoothCall state to the current Headset. 795 * 796 * @param force {@code true} if the headset state should be sent regardless if no changes to 797 * the state have occurred, {@code false} if the state should only be sent if the state 798 * has changed. 799 */ updateHeadsetWithCallState(boolean force)800 private void updateHeadsetWithCallState(boolean force) { 801 BluetoothCall activeCall = mCallInfo.getActiveCall(); 802 BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall(); 803 BluetoothCall heldCall = mCallInfo.getHeldCall(); 804 805 int bluetoothCallState = getBluetoothCallStateForUpdate(); 806 807 String ringingAddress = null; 808 int ringingAddressType = DEFAULT_RINGING_ADDRESS_TYPE; 809 String ringingName = null; 810 if (!mCallInfo.isNullCall(ringingCall) && ringingCall.getHandle() != null 811 && !ringingCall.isSilentRingingRequested()) { 812 ringingAddress = ringingCall.getHandle().getSchemeSpecificPart(); 813 if (ringingAddress != null) { 814 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress); 815 } 816 ringingName = ringingCall.getCallerDisplayName(); 817 if (TextUtils.isEmpty(ringingName)) { 818 ringingName = ringingCall.getContactDisplayName(); 819 } 820 } 821 if (ringingAddress == null) { 822 ringingAddress = ""; 823 } 824 825 int numActiveCalls = mCallInfo.isNullCall(activeCall) ? 0 : 1; 826 int numHeldCalls = mCallInfo.getNumHeldCalls(); 827 int numChildrenOfActiveCall = 828 mCallInfo.isNullCall(activeCall) ? 0 : activeCall.getChildrenIds().size(); 829 830 // Intermediate state for GSM calls which are in the process of being swapped. 831 // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls 832 // are held? 833 boolean callsPendingSwitch = (numHeldCalls == 2); 834 835 // For conference calls which support swapping the active BluetoothCall within the 836 // conference (namely CDMA calls) we need to expose that as a held BluetoothCall 837 // in order for the BT device to show "swap" and "merge" functionality. 838 boolean ignoreHeldCallChange = false; 839 if (!mCallInfo.isNullCall(activeCall) && activeCall.isConference() 840 && !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) { 841 if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) { 842 // Indicate that BT device should show SWAP command by indicating that there is a 843 // BluetoothCall on hold, but only if the conference wasn't previously merged. 844 numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1; 845 } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) { 846 numHeldCalls = 1; // Merge is available, so expose via numHeldCalls. 847 } 848 849 for (String id : activeCall.getChildrenIds()) { 850 // Held BluetoothCall has changed due to it being combined into a CDMA conference. 851 // Keep track of this and ignore any future update since it doesn't really count 852 // as a BluetoothCall change. 853 if (mOldHeldCall != null && mOldHeldCall.getTelecomCallId() == id) { 854 ignoreHeldCallChange = true; 855 break; 856 } 857 } 858 } 859 860 if (mBluetoothHeadset != null 861 && (force 862 || (!callsPendingSwitch 863 && (numActiveCalls != mNumActiveCalls 864 || numChildrenOfActiveCall != mNumChildrenOfActiveCall 865 || numHeldCalls != mNumHeldCalls 866 || bluetoothCallState != mBluetoothCallState 867 || !TextUtils.equals(ringingAddress, mRingingAddress) 868 || ringingAddressType != mRingingAddressType 869 || (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) { 870 871 // If the BluetoothCall is transitioning into the alerting state, send DIALING first. 872 // Some devices expect to see a DIALING state prior to seeing an ALERTING state 873 // so we need to send it first. 874 boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState 875 && bluetoothCallState == CALL_STATE_ALERTING; 876 877 mOldHeldCall = heldCall; 878 mNumActiveCalls = numActiveCalls; 879 mNumChildrenOfActiveCall = numChildrenOfActiveCall; 880 mNumHeldCalls = numHeldCalls; 881 mBluetoothCallState = bluetoothCallState; 882 mRingingAddress = ringingAddress; 883 mRingingAddressType = ringingAddressType; 884 885 if (sendDialingFirst) { 886 // Log in full to make logs easier to debug. 887 Log.i(TAG, "updateHeadsetWithCallState " 888 + "numActive " + mNumActiveCalls + ", " 889 + "numHeld " + mNumHeldCalls + ", " 890 + "callState " + CALL_STATE_DIALING + ", " 891 + "ringing type " + mRingingAddressType); 892 mBluetoothHeadset.phoneStateChanged( 893 mNumActiveCalls, 894 mNumHeldCalls, 895 CALL_STATE_DIALING, 896 mRingingAddress, 897 mRingingAddressType, 898 ringingName); 899 } 900 901 Log.i(TAG, "updateHeadsetWithCallState " 902 + "numActive " + mNumActiveCalls + ", " 903 + "numHeld " + mNumHeldCalls + ", " 904 + "callState " + mBluetoothCallState + ", " 905 + "ringing type " + mRingingAddressType); 906 907 mBluetoothHeadset.phoneStateChanged( 908 mNumActiveCalls, 909 mNumHeldCalls, 910 mBluetoothCallState, 911 mRingingAddress, 912 mRingingAddressType, 913 ringingName); 914 915 mHeadsetUpdatedRecently = true; 916 } 917 } 918 getBluetoothCallStateForUpdate()919 private int getBluetoothCallStateForUpdate() { 920 BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall(); 921 BluetoothCall dialingCall = mCallInfo.getOutgoingCall(); 922 boolean hasOnlyDisconnectedCalls = mCallInfo.hasOnlyDisconnectedCalls(); 923 924 // 925 // !! WARNING !! 926 // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not 927 // used in this version of the BluetoothCall state mappings. This is on purpose. 928 // phone_state_change() in btif_hf.c is not written to handle these states. Only with the 929 // listCalls*() method are WAITING and ACTIVE used. 930 // Using the unsupported states here caused problems with inconsistent state in some 931 // bluetooth devices (like not getting out of ringing state after answering a call). 932 // 933 int bluetoothCallState = CALL_STATE_IDLE; 934 if (!mCallInfo.isNullCall(ringingCall) && !ringingCall.isSilentRingingRequested()) { 935 bluetoothCallState = CALL_STATE_INCOMING; 936 } else if (!mCallInfo.isNullCall(dialingCall)) { 937 bluetoothCallState = CALL_STATE_ALERTING; 938 } else if (hasOnlyDisconnectedCalls || mIsDisconnectedTonePlaying) { 939 // Keep the DISCONNECTED state until the disconnect tone's playback is done 940 bluetoothCallState = CALL_STATE_DISCONNECTED; 941 } 942 return bluetoothCallState; 943 } 944 getBtCallState(BluetoothCall call, boolean isForeground)945 private int getBtCallState(BluetoothCall call, boolean isForeground) { 946 switch (call.getState()) { 947 case Call.STATE_NEW: 948 case Call.STATE_DISCONNECTED: 949 case Call.STATE_AUDIO_PROCESSING: 950 return CALL_STATE_IDLE; 951 952 case Call.STATE_ACTIVE: 953 return CALL_STATE_ACTIVE; 954 955 case Call.STATE_CONNECTING: 956 case Call.STATE_SELECT_PHONE_ACCOUNT: 957 case Call.STATE_DIALING: 958 case Call.STATE_PULLING_CALL: 959 // Yes, this is correctly returning ALERTING. 960 // "Dialing" for BT means that we have sent information to the service provider 961 // to place the BluetoothCall but there is no confirmation that the BluetoothCall 962 // is going through. When there finally is confirmation, the ringback is 963 // played which is referred to as an "alert" tone, thus, ALERTING. 964 // TODO: We should consider using the ALERTING terms in Telecom because that 965 // seems to be more industry-standard. 966 return CALL_STATE_ALERTING; 967 968 case Call.STATE_HOLDING: 969 return CALL_STATE_HELD; 970 971 case Call.STATE_RINGING: 972 case Call.STATE_SIMULATED_RINGING: 973 if (call.isSilentRingingRequested()) { 974 return CALL_STATE_IDLE; 975 } else if (isForeground) { 976 return CALL_STATE_INCOMING; 977 } else { 978 return CALL_STATE_WAITING; 979 } 980 } 981 return CALL_STATE_IDLE; 982 } 983 984 @VisibleForTesting getCallback(BluetoothCall call)985 public CallStateCallback getCallback(BluetoothCall call) { 986 return mCallbacks.get(call.getTelecomCallId()); 987 } 988 989 @VisibleForTesting setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset)990 public void setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset) { 991 mBluetoothHeadset = bluetoothHeadset; 992 } 993 994 @VisibleForTesting getBluetoothCallById(String id)995 public BluetoothCall getBluetoothCallById(String id) { 996 if (mBluetoothCallHashMap.containsKey(id)) { 997 return mBluetoothCallHashMap.get(id); 998 } 999 return null; 1000 } 1001 1002 @VisibleForTesting getBluetoothCallsByIds(List<String> ids)1003 public List<BluetoothCall> getBluetoothCallsByIds(List<String> ids) { 1004 List<BluetoothCall> calls = new ArrayList<>(); 1005 for (String id : ids) { 1006 BluetoothCall call = getBluetoothCallById(id); 1007 if (!mCallInfo.isNullCall(call)) { 1008 calls.add(call); 1009 } 1010 } 1011 return calls; 1012 } 1013 1014 // extract call information functions out into this part, so we can mock it in testing 1015 @VisibleForTesting 1016 public class CallInfo { 1017 getForegroundCall()1018 public BluetoothCall getForegroundCall() { 1019 LinkedHashSet<Integer> states = new LinkedHashSet<Integer>(); 1020 BluetoothCall foregroundCall; 1021 1022 states.add(Call.STATE_CONNECTING); 1023 foregroundCall = getCallByStates(states); 1024 if (!mCallInfo.isNullCall(foregroundCall)) { 1025 return foregroundCall; 1026 } 1027 1028 states.clear(); 1029 states.add(Call.STATE_ACTIVE); 1030 states.add(Call.STATE_DIALING); 1031 states.add(Call.STATE_PULLING_CALL); 1032 foregroundCall = getCallByStates(states); 1033 if (!mCallInfo.isNullCall(foregroundCall)) { 1034 return foregroundCall; 1035 } 1036 1037 states.clear(); 1038 states.add(Call.STATE_RINGING); 1039 foregroundCall = getCallByStates(states); 1040 if (!mCallInfo.isNullCall(foregroundCall)) { 1041 return foregroundCall; 1042 } 1043 1044 return null; 1045 } 1046 getCallByStates(LinkedHashSet<Integer> states)1047 public BluetoothCall getCallByStates(LinkedHashSet<Integer> states) { 1048 List<BluetoothCall> calls = getBluetoothCalls(); 1049 for (BluetoothCall call : calls) { 1050 if (states.contains(call.getState())) { 1051 return call; 1052 } 1053 } 1054 return null; 1055 } 1056 getCallByState(int state)1057 public BluetoothCall getCallByState(int state) { 1058 List<BluetoothCall> calls = getBluetoothCalls(); 1059 for (BluetoothCall call : calls) { 1060 if (state == call.getState()) { 1061 return call; 1062 } 1063 } 1064 return null; 1065 } 1066 getNumHeldCalls()1067 public int getNumHeldCalls() { 1068 int number = 0; 1069 List<BluetoothCall> calls = getBluetoothCalls(); 1070 for (BluetoothCall call : calls) { 1071 if (call.getState() == Call.STATE_HOLDING) { 1072 number++; 1073 } 1074 } 1075 return number; 1076 } 1077 hasOnlyDisconnectedCalls()1078 public boolean hasOnlyDisconnectedCalls() { 1079 List<BluetoothCall> calls = getBluetoothCalls(); 1080 if (calls.size() == 0) { 1081 return false; 1082 } 1083 for (BluetoothCall call : calls) { 1084 if (call.getState() != Call.STATE_DISCONNECTED) { 1085 return false; 1086 } 1087 } 1088 return true; 1089 } 1090 getBluetoothCalls()1091 public List<BluetoothCall> getBluetoothCalls() { 1092 return getBluetoothCallsByIds(BluetoothCall.getIds(getCalls())); 1093 } 1094 getOutgoingCall()1095 public BluetoothCall getOutgoingCall() { 1096 LinkedHashSet<Integer> states = new LinkedHashSet<Integer>(); 1097 states.add(Call.STATE_CONNECTING); 1098 states.add(Call.STATE_DIALING); 1099 states.add(Call.STATE_PULLING_CALL); 1100 return getCallByStates(states); 1101 } 1102 getRingingOrSimulatedRingingCall()1103 public BluetoothCall getRingingOrSimulatedRingingCall() { 1104 LinkedHashSet<Integer> states = new LinkedHashSet<Integer>(); 1105 states.add(Call.STATE_RINGING); 1106 states.add(Call.STATE_SIMULATED_RINGING); 1107 return getCallByStates(states); 1108 } 1109 getActiveCall()1110 public BluetoothCall getActiveCall() { 1111 return getCallByState(Call.STATE_ACTIVE); 1112 } 1113 getHeldCall()1114 public BluetoothCall getHeldCall() { 1115 return getCallByState(Call.STATE_HOLDING); 1116 } 1117 1118 /** 1119 * Returns the best phone account to use for the given state of all calls. 1120 * First, tries to return the phone account for the foreground call, second the default 1121 * phone account for PhoneAccount.SCHEME_TEL. 1122 */ getBestPhoneAccount()1123 public PhoneAccount getBestPhoneAccount() { 1124 BluetoothCall call = getForegroundCall(); 1125 1126 PhoneAccount account = null; 1127 if (!mCallInfo.isNullCall(call)) { 1128 PhoneAccountHandle handle = call.getAccountHandle(); 1129 if (handle != null) { 1130 // First try to get the network name of the foreground call. 1131 account = mTelecomManager.getPhoneAccount(handle); 1132 } 1133 } 1134 1135 if (account == null) { 1136 // Second, Try to get the label for the default Phone Account. 1137 List<PhoneAccountHandle> handles = 1138 mTelecomManager.getPhoneAccountsSupportingScheme(PhoneAccount.SCHEME_TEL); 1139 while (handles.iterator().hasNext()) { 1140 account = mTelecomManager.getPhoneAccount(handles.iterator().next()); 1141 if (account != null) { 1142 return account; 1143 } 1144 } 1145 } 1146 return null; 1147 } 1148 isNullCall(BluetoothCall call)1149 public boolean isNullCall(BluetoothCall call) { 1150 return call == null || call.getCall() == null; 1151 } 1152 }; 1153 }; 1154