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.NonNull; 20 import android.annotation.RequiresPermission; 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothHeadset; 23 import android.bluetooth.BluetoothLeCall; 24 import android.bluetooth.BluetoothLeCallControl; 25 import android.bluetooth.BluetoothProfile; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.net.Uri; 31 import android.os.Binder; 32 import android.os.Bundle; 33 import android.os.IBinder; 34 import android.telecom.BluetoothCallQualityReport; 35 import android.telecom.Call; 36 import android.telecom.CallAudioState; 37 import android.telecom.Connection; 38 import android.telecom.DisconnectCause; 39 import android.telecom.InCallService; 40 import android.telecom.PhoneAccount; 41 import android.telecom.PhoneAccountHandle; 42 import android.telecom.TelecomManager; 43 import android.telecom.VideoProfile; 44 import android.telephony.PhoneNumberUtils; 45 import android.telephony.TelephonyManager; 46 import android.text.TextUtils; 47 import android.util.Log; 48 49 import androidx.annotation.VisibleForTesting; 50 51 import com.android.bluetooth.hfp.BluetoothHeadsetProxy; 52 import com.android.bluetooth.tbs.BluetoothLeCallControlProxy; 53 54 import java.util.ArrayDeque; 55 import java.util.ArrayList; 56 import java.util.Arrays; 57 import java.util.Collection; 58 import java.util.HashMap; 59 import java.util.LinkedHashSet; 60 import java.util.List; 61 import java.util.Objects; 62 import java.util.Queue; 63 import java.util.SortedMap; 64 import java.util.SortedSet; 65 import java.util.TreeMap; 66 import java.util.TreeSet; 67 import java.util.UUID; 68 import java.util.concurrent.ExecutorService; 69 import java.util.concurrent.Executors; 70 71 /** 72 * Used to receive updates about calls from the Telecom component. This service is bound to Telecom 73 * while there exist calls which potentially require UI. This includes ringing (incoming), dialing 74 * (outgoing), and active calls. When the last BluetoothCall is disconnected, Telecom will unbind to 75 * the service triggering InCallActivity (via CallList) to finish soon after. 76 */ 77 public class BluetoothInCallService extends InCallService { 78 79 private static final String TAG = "BluetoothInCallService"; 80 // match up with bthf_call_state_t of bt_hf.h 81 private static final int CALL_STATE_ACTIVE = 0; 82 private static final int CALL_STATE_HELD = 1; 83 private static final int CALL_STATE_DIALING = 2; 84 private static final int CALL_STATE_ALERTING = 3; 85 private static final int CALL_STATE_INCOMING = 4; 86 private static final int CALL_STATE_WAITING = 5; 87 private static final int CALL_STATE_IDLE = 6; 88 private static final int CALL_STATE_DISCONNECTED = 7; 89 90 // match up with bthf_call_state_t of bt_hf.h 91 // Terminate all held or set UDUB("busy") to a waiting call 92 private static final int CHLD_TYPE_RELEASEHELD = 0; 93 // Terminate all active calls and accepts a waiting/held call 94 private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1; 95 // Hold all active calls and accepts a waiting/held call 96 private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2; 97 // Add all held calls to a conference 98 private static final int CHLD_TYPE_ADDHELDTOCONF = 3; 99 100 // Indicates that no BluetoothCall is ringing 101 private static final int DEFAULT_RINGING_ADDRESS_TYPE = 128; 102 103 private int mNumActiveCalls = 0; 104 private int mNumHeldCalls = 0; 105 private int mNumChildrenOfActiveCall = 0; 106 private int mBluetoothCallState = CALL_STATE_IDLE; 107 private String mRingingAddress = ""; 108 private int mRingingAddressType = DEFAULT_RINGING_ADDRESS_TYPE; 109 private BluetoothCall mOldHeldCall = null; 110 private boolean mHeadsetUpdatedRecently = false; 111 private boolean mIsDisconnectedTonePlaying = false; 112 113 @VisibleForTesting boolean mIsTerminatedByClient = false; 114 115 private static final Object LOCK = new Object(); 116 117 @VisibleForTesting BluetoothHeadsetProxy mBluetoothHeadset; 118 119 @VisibleForTesting BluetoothLeCallControlProxy mBluetoothLeCallControl; 120 private ExecutorService mExecutor; 121 122 @VisibleForTesting public TelephonyManager mTelephonyManager; 123 124 @VisibleForTesting public TelecomManager mTelecomManager; 125 126 @VisibleForTesting 127 public final HashMap<Integer, CallStateCallback> mCallbacks = new HashMap<>(); 128 129 @VisibleForTesting 130 public final HashMap<Integer, BluetoothCall> mBluetoothCallHashMap = new HashMap<>(); 131 132 private final HashMap<Integer, BluetoothCall> mBluetoothConferenceCallInference = 133 new HashMap<>(); 134 135 // A queue record the removal order of bluetooth calls 136 private final Queue<Integer> mBluetoothCallQueue = new ArrayDeque<>(); 137 138 private static BluetoothInCallService sInstance = null; 139 140 public CallInfo mCallInfo = new CallInfo(); 141 142 protected boolean mOnCreateCalled = false; 143 144 private int mMaxNumberOfCalls = 0; 145 146 /** 147 * Listens to connections and disconnections of bluetooth headsets. We need to save the current 148 * bluetooth headset so that we know where to send BluetoothCall updates. 149 */ 150 @VisibleForTesting 151 public BluetoothProfile.ServiceListener mProfileListener = 152 new BluetoothProfile.ServiceListener() { 153 @Override 154 public void onServiceConnected(int profile, BluetoothProfile proxy) { 155 synchronized (LOCK) { 156 if (profile == BluetoothProfile.HEADSET) { 157 setBluetoothHeadset( 158 new BluetoothHeadsetProxy((BluetoothHeadset) proxy)); 159 updateHeadsetWithCallState(true /* force */); 160 } else { 161 setBluetoothLeCallControl( 162 new BluetoothLeCallControlProxy( 163 (BluetoothLeCallControl) proxy)); 164 sendTbsCurrentCallsList(); 165 } 166 } 167 } 168 169 @Override 170 public void onServiceDisconnected(int profile) { 171 synchronized (LOCK) { 172 if (profile == BluetoothProfile.HEADSET) { 173 setBluetoothHeadset(null); 174 } else { 175 setBluetoothLeCallControl(null); 176 } 177 } 178 } 179 }; 180 181 public class BluetoothAdapterReceiver extends BroadcastReceiver { 182 @Override onReceive(Context context, Intent intent)183 public void onReceive(Context context, Intent intent) { 184 synchronized (LOCK) { 185 if (intent.getAction() != BluetoothAdapter.ACTION_STATE_CHANGED) { 186 Log.w(TAG, "BluetoothAdapterReceiver: Intent action " + intent.getAction()); 187 return; 188 } 189 int state = 190 intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 191 Log.d(TAG, "Bluetooth Adapter state: " + state); 192 if (state == BluetoothAdapter.STATE_ON) { 193 queryPhoneState(); 194 } else if (state == BluetoothAdapter.STATE_TURNING_OFF) { 195 clear(); 196 } 197 } 198 } 199 } 200 ; 201 202 /** Receives events for global state changes of the bluetooth adapter. */ 203 // TODO: The code is moved from Telecom stack. Since we're running in the BT process itself, 204 // we may be able to simplify this in a future patch. 205 @VisibleForTesting public BluetoothAdapterReceiver mBluetoothAdapterReceiver; 206 207 @VisibleForTesting 208 public class CallStateCallback extends Call.Callback { 209 public int mLastState; 210 CallStateCallback(int initialState)211 public CallStateCallback(int initialState) { 212 mLastState = initialState; 213 } 214 getLastState()215 public int getLastState() { 216 return mLastState; 217 } 218 onStateChanged(BluetoothCall call, int state)219 public void onStateChanged(BluetoothCall call, int state) { 220 if (mCallInfo.isNullCall(call)) { 221 return; 222 } 223 if (call.isExternalCall()) { 224 return; 225 } 226 if (state == Call.STATE_DISCONNECTING) { 227 mLastState = state; 228 return; 229 } 230 231 Integer tbsCallState = getTbsCallState(call); 232 if (mBluetoothLeCallControl != null && tbsCallState != null) { 233 mBluetoothLeCallControl.onCallStateChanged(call.getTbsCallId(), tbsCallState); 234 } 235 236 // If a BluetoothCall is being put on hold because of a new connecting call, ignore the 237 // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing 238 // state atomically. 239 // When the BluetoothCall later transitions to DIALING/DISCONNECTED we will then 240 // send out the aggregated update. 241 if (getLastState() == Call.STATE_ACTIVE && state == Call.STATE_HOLDING) { 242 for (BluetoothCall otherCall : mCallInfo.getBluetoothCalls()) { 243 if (otherCall.getState() == Call.STATE_CONNECTING) { 244 mLastState = state; 245 return; 246 } 247 } 248 } 249 250 // To have an active BluetoothCall and another dialing at the same time is an invalid BT 251 // state. We can assume that the active BluetoothCall will be automatically held 252 // which will send another update at which point we will be in the right state. 253 BluetoothCall activeCall = mCallInfo.getActiveCall(); 254 if (!mCallInfo.isNullCall(activeCall) 255 && getLastState() == Call.STATE_CONNECTING 256 && (state == Call.STATE_DIALING || state == Call.STATE_PULLING_CALL)) { 257 mLastState = state; 258 return; 259 } 260 mLastState = state; 261 updateHeadsetWithCallState(false /* force */); 262 } 263 264 @Override onStateChanged(Call call, int state)265 public void onStateChanged(Call call, int state) { 266 super.onStateChanged(call, state); 267 onStateChanged(getBluetoothCallById(System.identityHashCode(call)), state); 268 } 269 onDetailsChanged(BluetoothCall call, Call.Details details)270 public void onDetailsChanged(BluetoothCall call, Call.Details details) { 271 if (mCallInfo.isNullCall(call)) { 272 return; 273 } 274 if (call.isExternalCall()) { 275 onCallRemoved(call, false /* forceRemoveCallback */); 276 } else { 277 onCallAdded(call); 278 } 279 } 280 281 @Override onDetailsChanged(Call call, Call.Details details)282 public void onDetailsChanged(Call call, Call.Details details) { 283 super.onDetailsChanged(call, details); 284 onDetailsChanged(getBluetoothCallById(System.identityHashCode(call)), details); 285 } 286 onParentChanged(BluetoothCall call)287 public void onParentChanged(BluetoothCall call) { 288 if (mCallInfo.isNullCall(call) || call.isExternalCall()) { 289 Log.w(TAG, "null call or external call"); 290 return; 291 } 292 if (call.getParentId() != null) { 293 // If this BluetoothCall is newly conferenced, ignore the callback. 294 // We only care about the one sent for the parent conference call. 295 Log.d( 296 TAG, 297 "Ignoring onIsConferenceChanged from child BluetoothCall with new parent"); 298 return; 299 } 300 updateHeadsetWithCallState(false /* force */); 301 } 302 303 @Override onParentChanged(Call call, Call parent)304 public void onParentChanged(Call call, Call parent) { 305 super.onParentChanged(call, parent); 306 onParentChanged(getBluetoothCallById(System.identityHashCode(call))); 307 } 308 onChildrenChanged(BluetoothCall call, List<BluetoothCall> children)309 public void onChildrenChanged(BluetoothCall call, List<BluetoothCall> children) { 310 if (mCallInfo.isNullCall(call) || call.isExternalCall()) { 311 Log.w(TAG, "null call or external call"); 312 return; 313 } 314 if (call.getChildrenIds().size() == 1) { 315 // If this is a parent BluetoothCall with only one child, 316 // ignore the callback as well since the minimum number of child calls to 317 // start a conference BluetoothCall is 2. We expect this to be called again 318 // when the parent BluetoothCall has another child BluetoothCall added. 319 Log.d(TAG, "Ignoring onIsConferenceChanged from parent with only one child call"); 320 return; 321 } 322 updateHeadsetWithCallState(false /* force */); 323 } 324 325 @Override onChildrenChanged(Call call, List<Call> children)326 public void onChildrenChanged(Call call, List<Call> children) { 327 super.onChildrenChanged(call, children); 328 onChildrenChanged( 329 getBluetoothCallById(System.identityHashCode(call)), 330 getBluetoothCallsByIds(BluetoothCall.getIds(children))); 331 } 332 } 333 334 @Override onBind(Intent intent)335 public IBinder onBind(Intent intent) { 336 Log.i(TAG, "onBind. Intent: " + intent); 337 IBinder binder = super.onBind(intent); 338 mTelephonyManager = getSystemService(TelephonyManager.class); 339 mTelecomManager = getSystemService(TelecomManager.class); 340 return binder; 341 } 342 343 @Override onUnbind(Intent intent)344 public boolean onUnbind(Intent intent) { 345 Log.i(TAG, "onUnbind. Intent: " + intent); 346 return super.onUnbind(intent); 347 } 348 BluetoothInCallService()349 public BluetoothInCallService() { 350 Log.i(TAG, "BluetoothInCallService is created"); 351 sInstance = this; 352 mExecutor = Executors.newSingleThreadExecutor(); 353 } 354 getInstance()355 public static BluetoothInCallService getInstance() { 356 return sInstance; 357 } 358 359 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) enforceModifyPermission()360 protected void enforceModifyPermission() { 361 enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null); 362 } 363 364 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) answerCall()365 public boolean answerCall() { 366 synchronized (LOCK) { 367 enforceModifyPermission(); 368 Log.i(TAG, "BT - answering call"); 369 BluetoothCall call = mCallInfo.getRingingOrSimulatedRingingCall(); 370 if (mCallInfo.isNullCall(call)) { 371 return false; 372 } 373 call.answer(VideoProfile.STATE_AUDIO_ONLY); 374 return true; 375 } 376 } 377 378 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) hangupCall()379 public boolean hangupCall() { 380 synchronized (LOCK) { 381 enforceModifyPermission(); 382 Log.i(TAG, "BT - hanging up call"); 383 BluetoothCall call = mCallInfo.getForegroundCall(); 384 if (mCallInfo.isNullCall(call)) { 385 return false; 386 } 387 // release the parent if there is a conference call 388 BluetoothCall conferenceCall = getBluetoothCallById(call.getParentId()); 389 if (!mCallInfo.isNullCall(conferenceCall) 390 && conferenceCall.getState() == Call.STATE_ACTIVE) { 391 Log.i(TAG, "BT - hanging up conference call"); 392 call = conferenceCall; 393 } 394 if (call.getState() == Call.STATE_RINGING) { 395 call.reject(false, ""); 396 } else { 397 call.disconnect(); 398 } 399 return true; 400 } 401 } 402 403 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) sendDtmf(int dtmf)404 public boolean sendDtmf(int dtmf) { 405 synchronized (LOCK) { 406 enforceModifyPermission(); 407 Log.i(TAG, "BT - sendDtmf " + dtmf); 408 BluetoothCall call = mCallInfo.getForegroundCall(); 409 if (mCallInfo.isNullCall(call)) { 410 return false; 411 } 412 // TODO: Consider making this a queue instead of starting/stopping 413 // in quick succession. 414 call.playDtmfTone((char) dtmf); 415 call.stopDtmfTone(); 416 return true; 417 } 418 } 419 420 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) getNetworkOperator()421 public String getNetworkOperator() { 422 synchronized (LOCK) { 423 enforceModifyPermission(); 424 Log.i(TAG, "getNetworkOperator"); 425 PhoneAccount account = mCallInfo.getBestPhoneAccount(); 426 if (account != null && account.getLabel() != null) { 427 return account.getLabel().toString(); 428 } 429 // Finally, just get the network name from telephony. 430 return mTelephonyManager.getNetworkOperatorName(); 431 } 432 } 433 434 /** 435 * Gets the brearer technology. 436 * 437 * @return bearer technology as defined in Bluetooth Assigned Numbers 438 */ 439 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) getBearerTechnology()440 public int getBearerTechnology() { 441 synchronized (LOCK) { 442 enforceModifyPermission(); 443 Log.i(TAG, "getBearerTechnology"); 444 // Get the network name from telephony. 445 int dataNetworkType = mTelephonyManager.getDataNetworkType(); 446 switch (dataNetworkType) { 447 case TelephonyManager.NETWORK_TYPE_UNKNOWN: 448 case TelephonyManager.NETWORK_TYPE_GSM: 449 return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_GSM; 450 451 case TelephonyManager.NETWORK_TYPE_GPRS: 452 return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_2G; 453 454 case TelephonyManager.NETWORK_TYPE_EDGE: 455 case TelephonyManager.NETWORK_TYPE_EVDO_0: 456 case TelephonyManager.NETWORK_TYPE_EVDO_A: 457 case TelephonyManager.NETWORK_TYPE_HSDPA: 458 case TelephonyManager.NETWORK_TYPE_HSUPA: 459 case TelephonyManager.NETWORK_TYPE_HSPA: 460 case TelephonyManager.NETWORK_TYPE_IDEN: 461 case TelephonyManager.NETWORK_TYPE_EVDO_B: 462 return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_3G; 463 464 case TelephonyManager.NETWORK_TYPE_UMTS: 465 case TelephonyManager.NETWORK_TYPE_TD_SCDMA: 466 return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_WCDMA; 467 468 case TelephonyManager.NETWORK_TYPE_LTE: 469 return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_LTE; 470 471 case TelephonyManager.NETWORK_TYPE_EHRPD: 472 case TelephonyManager.NETWORK_TYPE_CDMA: 473 case TelephonyManager.NETWORK_TYPE_1xRTT: 474 return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_CDMA; 475 476 case TelephonyManager.NETWORK_TYPE_HSPAP: 477 return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_4G; 478 479 case TelephonyManager.NETWORK_TYPE_IWLAN: 480 return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_WIFI; 481 482 case TelephonyManager.NETWORK_TYPE_NR: 483 return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_5G; 484 } 485 486 return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_GSM; 487 } 488 } 489 490 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) getSubscriberNumber()491 public String getSubscriberNumber() { 492 synchronized (LOCK) { 493 enforceModifyPermission(); 494 Log.i(TAG, "getSubscriberNumber"); 495 String address = null; 496 PhoneAccount account = mCallInfo.getBestPhoneAccount(); 497 if (account != null) { 498 Uri addressUri = account.getAddress(); 499 if (addressUri != null) { 500 address = addressUri.getSchemeSpecificPart(); 501 } 502 } 503 if (TextUtils.isEmpty(address)) { 504 if (mTelephonyManager == null) { 505 address = null; 506 } else { 507 address = mTelephonyManager.getLine1Number(); 508 } 509 if (address == null) address = ""; 510 } 511 return address; 512 } 513 } 514 515 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) listCurrentCalls()516 public boolean listCurrentCalls() { 517 synchronized (LOCK) { 518 if (!mOnCreateCalled) { 519 Log.w(TAG, "listcurrentCalls() is called before onCreate()"); 520 return false; 521 } 522 enforceModifyPermission(); 523 // only log if it is after we recently updated the headset state or else it can 524 // clog the android log since this can be queried every second. 525 boolean logQuery = mHeadsetUpdatedRecently; 526 mHeadsetUpdatedRecently = false; 527 528 if (logQuery) { 529 Log.i(TAG, "listcurrentCalls"); 530 } 531 532 sendListOfCalls(logQuery); 533 return true; 534 } 535 } 536 537 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) queryPhoneState()538 public boolean queryPhoneState() { 539 synchronized (LOCK) { 540 enforceModifyPermission(); 541 Log.i(TAG, "queryPhoneState"); 542 updateHeadsetWithCallState(true); 543 return true; 544 } 545 } 546 547 /** Check for HD codec for voice call */ isHighDefCallInProgress()548 public boolean isHighDefCallInProgress() { 549 boolean isHighDef = false; 550 /* TODO: Add as an API in TelephonyManager aosp/2679237 */ 551 int phoneTypeIms = 5; 552 int phoneTypeCdmaLte = 6; 553 BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall(); 554 BluetoothCall dialingCall = mCallInfo.getOutgoingCall(); 555 BluetoothCall activeCall = mCallInfo.getActiveCall(); 556 557 /* If it's an incoming call we will have codec info in dialing state */ 558 if (ringingCall != null) { 559 isHighDef = ringingCall.isHighDefAudio(); 560 } else if (dialingCall != null) { 561 /* CS dialing call has codec info in dialing state */ 562 Bundle extras = dialingCall.getDetails().getExtras(); 563 if (extras != null) { 564 int phoneType = extras.getInt(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE); 565 if (phoneType == TelephonyManager.PHONE_TYPE_GSM 566 || phoneType == TelephonyManager.PHONE_TYPE_CDMA) { 567 isHighDef = dialingCall.isHighDefAudio(); 568 /* For IMS calls codec info is not present in dialing state */ 569 } else if (phoneType == phoneTypeIms || phoneType == phoneTypeCdmaLte) { 570 isHighDef = true; 571 } 572 } 573 } else if (activeCall != null) { 574 isHighDef = activeCall.isHighDefAudio(); 575 } 576 Log.i(TAG, "isHighDefCallInProgress: Call is High Def " + isHighDef); 577 return isHighDef; 578 } 579 580 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) processChld(int chld)581 public boolean processChld(int chld) { 582 synchronized (LOCK) { 583 enforceModifyPermission(); 584 final long token = Binder.clearCallingIdentity(); 585 try { 586 Log.i(TAG, "processChld " + chld); 587 return _processChld(chld); 588 } finally { 589 Binder.restoreCallingIdentity(token); 590 } 591 } 592 } 593 onCallAdded(BluetoothCall call)594 public void onCallAdded(BluetoothCall call) { 595 if (call.isExternalCall()) { 596 return; 597 } 598 if (!mBluetoothCallHashMap.containsKey(call.getId())) { 599 Log.d(TAG, "onCallAdded"); 600 CallStateCallback callback = new CallStateCallback(call.getState()); 601 mCallbacks.put(call.getId(), callback); 602 call.registerCallback(callback); 603 604 mBluetoothCallHashMap.put(call.getId(), call); 605 if (!call.isConference()) { 606 mMaxNumberOfCalls = Integer.max(mMaxNumberOfCalls, mBluetoothCallHashMap.size()); 607 } 608 updateHeadsetWithCallState(false /* force */); 609 610 BluetoothLeCall tbsCall = createTbsCall(call); 611 if (mBluetoothLeCallControl != null && tbsCall != null) { 612 mBluetoothLeCallControl.onCallAdded(tbsCall); 613 } 614 } 615 } 616 sendBluetoothCallQualityReport( long timestamp, int rssi, int snr, int retransmissionCount, int packetsNotReceiveCount, int negativeAcknowledgementCount)617 public void sendBluetoothCallQualityReport( 618 long timestamp, 619 int rssi, 620 int snr, 621 int retransmissionCount, 622 int packetsNotReceiveCount, 623 int negativeAcknowledgementCount) { 624 BluetoothCall call = mCallInfo.getForegroundCall(); 625 if (mCallInfo.isNullCall(call)) { 626 Log.w(TAG, "No foreground call while trying to send BQR"); 627 return; 628 } 629 Bundle b = new Bundle(); 630 b.putParcelable( 631 BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT, 632 new BluetoothCallQualityReport.Builder() 633 .setSentTimestampMillis(timestamp) 634 .setChoppyVoice(true) 635 .setRssiDbm(rssi) 636 .setSnrDb(snr) 637 .setRetransmittedPacketsCount(retransmissionCount) 638 .setPacketsNotReceivedCount(packetsNotReceiveCount) 639 .setNegativeAcknowledgementCount(negativeAcknowledgementCount) 640 .build()); 641 call.sendCallEvent(BluetoothCallQualityReport.EVENT_BLUETOOTH_CALL_QUALITY_REPORT, b); 642 } 643 644 @Override onCallAdded(Call call)645 public void onCallAdded(Call call) { 646 super.onCallAdded(call); 647 onCallAdded(new BluetoothCall(call)); 648 } 649 650 /** 651 * Called when a {@code BluetoothCall} has been removed from this in-call session. 652 * 653 * @param call the {@code BluetoothCall} to remove 654 * @param forceRemoveCallback if true, this will always unregister this {@code InCallService} as 655 * a callback for the given {@code BluetoothCall}, when false, this will not remove the 656 * callback when the {@code BluetoothCall} is external so that the call can be added back if 657 * no longer external. 658 */ onCallRemoved(BluetoothCall call, boolean forceRemoveCallback)659 public void onCallRemoved(BluetoothCall call, boolean forceRemoveCallback) { 660 Log.d(TAG, "onCallRemoved"); 661 CallStateCallback callback = getCallback(call); 662 if (callback != null && (forceRemoveCallback || !call.isExternalCall())) { 663 call.unregisterCallback(callback); 664 } 665 666 if (mBluetoothCallHashMap.containsKey(call.getId())) { 667 mBluetoothCallHashMap.remove(call.getId()); 668 669 DisconnectCause cause = call.getDisconnectCause(); 670 if (cause != null && cause.getCode() == DisconnectCause.OTHER) { 671 Log.d(TAG, "add inference call with reason: " + cause.getReason()); 672 mBluetoothCallQueue.add(call.getId()); 673 mBluetoothConferenceCallInference.put(call.getId(), call); 674 // queue size limited to 2 because merge operation only happens on 2 calls 675 // we are only interested in last 2 calls merged 676 if (mBluetoothCallQueue.size() > 2) { 677 Integer callId = mBluetoothCallQueue.peek(); 678 mBluetoothCallQueue.remove(); 679 mBluetoothConferenceCallInference.remove(callId); 680 } 681 } 682 // As there is at most 1 conference call, so clear inference when parent call ends 683 if (call.isConference()) { 684 Log.d(TAG, "conference call ends, clear inference"); 685 mBluetoothConferenceCallInference.clear(); 686 mBluetoothCallQueue.clear(); 687 } 688 } 689 690 updateHeadsetWithCallState(false /* force */); 691 692 if (mBluetoothLeCallControl != null) { 693 mBluetoothLeCallControl.onCallRemoved( 694 call.getTbsCallId(), getTbsTerminationReason(call)); 695 } 696 } 697 698 @Override onCallRemoved(Call call)699 public void onCallRemoved(Call call) { 700 super.onCallRemoved(call); 701 BluetoothCall bluetoothCall = getBluetoothCallById(System.identityHashCode(call)); 702 if (bluetoothCall == null) { 703 Log.w(TAG, "onCallRemoved, BluetoothCall is removed before registered"); 704 return; 705 } 706 onCallRemoved(bluetoothCall, true /* forceRemoveCallback */); 707 } 708 709 @Override onCallAudioStateChanged(CallAudioState audioState)710 public void onCallAudioStateChanged(CallAudioState audioState) { 711 super.onCallAudioStateChanged(audioState); 712 Log.d(TAG, "onCallAudioStateChanged, audioState == " + audioState); 713 } 714 715 @Override onCreate()716 public void onCreate() { 717 Log.d(TAG, "onCreate"); 718 super.onCreate(); 719 BluetoothAdapter.getDefaultAdapter() 720 .getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET); 721 BluetoothAdapter.getDefaultAdapter() 722 .getProfileProxy(this, mProfileListener, BluetoothProfile.LE_CALL_CONTROL); 723 mBluetoothAdapterReceiver = new BluetoothAdapterReceiver(); 724 IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); 725 intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 726 registerReceiver(mBluetoothAdapterReceiver, intentFilter); 727 mOnCreateCalled = true; 728 } 729 730 @Override onDestroy()731 public void onDestroy() { 732 Log.d(TAG, "onDestroy"); 733 clear(); 734 mOnCreateCalled = false; 735 super.onDestroy(); 736 } 737 738 @Override 739 @VisibleForTesting attachBaseContext(Context base)740 public void attachBaseContext(Context base) { 741 super.attachBaseContext(base); 742 } 743 744 @VisibleForTesting clear()745 void clear() { 746 Log.d(TAG, "clear"); 747 if (mBluetoothAdapterReceiver != null) { 748 unregisterReceiver(mBluetoothAdapterReceiver); 749 mBluetoothAdapterReceiver = null; 750 } 751 if (mBluetoothHeadset != null) { 752 mBluetoothHeadset.closeBluetoothHeadsetProxy(this); 753 mBluetoothHeadset = null; 754 } 755 if (mBluetoothLeCallControl != null) { 756 mBluetoothLeCallControl.unregisterBearer(); 757 mBluetoothLeCallControl.closeBluetoothLeCallControlProxy(this); 758 } 759 mProfileListener = null; 760 sInstance = null; 761 mCallbacks.clear(); 762 mBluetoothCallHashMap.clear(); 763 mBluetoothConferenceCallInference.clear(); 764 mBluetoothCallQueue.clear(); 765 mMaxNumberOfCalls = 0; 766 } 767 isConferenceWithNoChildren(BluetoothCall call)768 private static boolean isConferenceWithNoChildren(BluetoothCall call) { 769 return call.isConference() 770 && (call.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN) 771 || call.getChildrenIds().isEmpty()); 772 } 773 sendListOfCalls(boolean shouldLog)774 private void sendListOfCalls(boolean shouldLog) { 775 Collection<BluetoothCall> calls = mCallInfo.getBluetoothCalls(); 776 777 // either do conference call CLCC index inference or normal conference call 778 BluetoothCall conferenceCallChildrenNotReady = null; 779 for (BluetoothCall call : calls) { 780 // find the conference call parent among calls 781 if (call.isConference() && !mBluetoothConferenceCallInference.isEmpty()) { 782 Log.d( 783 TAG, 784 "conference call inferred size: " 785 + mBluetoothConferenceCallInference.size() 786 + " current size: " 787 + mBluetoothCallHashMap.size()); 788 // Do conference call inference until at least 2 children arrive 789 // If carrier sends children info, then inference will end when info arrives. 790 // If carrier doesn't send children info, then inference won't impact actual value. 791 if (call.getChildrenIds().size() >= 2) { 792 mBluetoothConferenceCallInference.clear(); 793 break; 794 } 795 conferenceCallChildrenNotReady = call; 796 } 797 } 798 if (conferenceCallChildrenNotReady != null) { 799 SortedMap<Integer, Object[]> clccResponseMap = new TreeMap<>(); 800 for (BluetoothCall inferredCall : mBluetoothConferenceCallInference.values()) { 801 if (inferredCall.isCallNull() || inferredCall.getHandle() == null) { 802 Log.w(TAG, "inferredCall does not have handle"); 803 continue; 804 } 805 // save the index so later on when real children arrive, index is the same 806 int index = inferredCall.mClccIndex; 807 if (index == -1) { 808 Log.w(TAG, "inferred index is not valid"); 809 continue; 810 } 811 812 // associate existing bluetoothCall with inferredCall based on call handle 813 for (BluetoothCall bluetoothCall : mBluetoothCallHashMap.values()) { 814 if (bluetoothCall.getHandle() == null) { 815 Log.w(TAG, "call id: " + bluetoothCall.getId() + " handle is null"); 816 continue; 817 } 818 if (mTelephonyManager == null) { 819 Log.w(TAG, "mTelephonyManager is null"); 820 continue; 821 } 822 boolean isSame = 823 PhoneNumberUtils.areSamePhoneNumber( 824 bluetoothCall.getHandle().toString(), 825 inferredCall.getHandle().toString(), 826 mTelephonyManager.getNetworkCountryIso()); 827 if (isSame) { 828 Log.d( 829 TAG, 830 "found conference call children that has same call handle, " 831 + "call id: " 832 + bluetoothCall.getId()); 833 bluetoothCall.mClccIndex = inferredCall.mClccIndex; 834 break; 835 } 836 } 837 838 int direction = inferredCall.isIncoming() ? 1 : 0; 839 int state = CALL_STATE_ACTIVE; 840 boolean isPartOfConference = true; 841 final Uri addressUri; 842 if (inferredCall.getGatewayInfo() != null) { 843 addressUri = inferredCall.getGatewayInfo().getOriginalAddress(); 844 } else { 845 addressUri = inferredCall.getHandle(); 846 } 847 String address = addressUri == null ? null : addressUri.getSchemeSpecificPart(); 848 if (address != null) { 849 address = PhoneNumberUtils.stripSeparators(address); 850 } 851 int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address); 852 clccResponseMap.put( 853 index, 854 new Object[] { 855 index, direction, state, 0, isPartOfConference, address, addressType 856 }); 857 } 858 // sort CLCC response based on index 859 for (Object[] response : clccResponseMap.values()) { 860 if (response.length < 7) { 861 Log.e(TAG, "clccResponseMap entry too short"); 862 continue; 863 } 864 Log.i( 865 TAG, 866 String.format( 867 "sending inferred clcc for BluetoothCall: index %d, direction" 868 + " %d, state %d, isPartOfConference %b, addressType %d", 869 (int) response[0], 870 (int) response[1], 871 (int) response[2], 872 (boolean) response[4], 873 (int) response[6])); 874 mBluetoothHeadset.clccResponse( 875 (int) response[0], 876 (int) response[1], 877 (int) response[2], 878 (int) response[3], 879 (boolean) response[4], 880 (String) response[5], 881 (int) response[6]); 882 } 883 sendClccEndMarker(); 884 return; 885 } 886 887 for (BluetoothCall call : calls) { 888 // We don't send the parent conference BluetoothCall to the bluetooth device. 889 // We do, however want to send conferences that have no children to the bluetooth 890 // device (e.g. IMS Conference). 891 boolean isConferenceWithNoChildren = isConferenceWithNoChildren(call); 892 Log.i( 893 TAG, 894 "sendListOfCalls isConferenceWithNoChildren " 895 + isConferenceWithNoChildren 896 + ", call.getChildrenIds() size " 897 + call.getChildrenIds().size()); 898 if (!call.isConference() || isConferenceWithNoChildren) { 899 sendClccForCall(call, shouldLog); 900 } 901 } 902 sendClccEndMarker(); 903 } 904 sendClccEndMarker()905 private void sendClccEndMarker() { 906 // End marker is recognized with an index value of 0. All other parameters are ignored. 907 if (mBluetoothHeadset != null) { 908 mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0); 909 } 910 } 911 912 /** Sends a single clcc (C* List Current Calls) event for the specified call. */ sendClccForCall(BluetoothCall call, boolean shouldLog)913 private void sendClccForCall(BluetoothCall call, boolean shouldLog) { 914 boolean isForeground = mCallInfo.getForegroundCall() == call; 915 int state = getBtCallState(call, isForeground); 916 boolean isPartOfConference = false; 917 boolean isConferenceWithNoChildren = isConferenceWithNoChildren(call); 918 919 if (state == CALL_STATE_IDLE) { 920 return; 921 } 922 923 BluetoothCall conferenceCall = getBluetoothCallById(call.getParentId()); 924 if (!mCallInfo.isNullCall(conferenceCall)) { 925 isPartOfConference = true; 926 927 if (conferenceCall.hasProperty(Call.Details.PROPERTY_GENERIC_CONFERENCE)) { 928 // Run some alternative states for CDMA Conference-level merge/swap support. 929 // Basically, if BluetoothCall supports swapping or merging at the conference-level, 930 // then we need to expose the calls as having distinct states 931 // (ACTIVE vs CAPABILITY_HOLD) or 932 // the functionality won't show up on the bluetooth device. 933 934 // Before doing any special logic, ensure that we are dealing with an 935 // ACTIVE BluetoothCall and that the conference itself has a notion of 936 // the current "active" child call. 937 BluetoothCall activeChild = 938 getBluetoothCallById( 939 conferenceCall.getGenericConferenceActiveChildCallId()); 940 if (state == CALL_STATE_ACTIVE && !mCallInfo.isNullCall(activeChild)) { 941 // Reevaluate state if we can MERGE or if we can SWAP without previously having 942 // MERGED. 943 boolean shouldReevaluateState = 944 conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) 945 || (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) 946 && !conferenceCall.wasConferencePreviouslyMerged()); 947 948 if (shouldReevaluateState) { 949 isPartOfConference = false; 950 if (call == activeChild) { 951 state = CALL_STATE_ACTIVE; 952 } else { 953 // At this point we know there is an "active" child and we know that it 954 // is not this call, so set it to HELD instead. 955 state = CALL_STATE_HELD; 956 } 957 } 958 } 959 } 960 if (conferenceCall.getState() == Call.STATE_HOLDING 961 && conferenceCall.can(Connection.CAPABILITY_MANAGE_CONFERENCE)) { 962 // If the parent IMS CEP conference BluetoothCall is on hold, we should mark 963 // this BluetoothCall as being on hold regardless of what the other 964 // children are doing. 965 state = CALL_STATE_HELD; 966 } 967 } else if (isConferenceWithNoChildren) { 968 // Handle the special case of an IMS conference BluetoothCall without conference 969 // event package support. 970 // The BluetoothCall will be marked as a conference, but the conference will not have 971 // child calls where conference event packages are not used by the carrier. 972 isPartOfConference = true; 973 } 974 975 int index = getIndexForCall(call); 976 int direction = call.isIncoming() ? 1 : 0; 977 final Uri addressUri; 978 if (call.getGatewayInfo() != null) { 979 addressUri = call.getGatewayInfo().getOriginalAddress(); 980 } else { 981 addressUri = call.getHandle(); 982 } 983 984 String address = addressUri == null ? null : addressUri.getSchemeSpecificPart(); 985 if (address != null) { 986 address = PhoneNumberUtils.stripSeparators(address); 987 } 988 989 int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address); 990 991 if (shouldLog) { 992 Log.i( 993 TAG, 994 "sending clcc for BluetoothCall " 995 + index 996 + ", " 997 + direction 998 + ", " 999 + state 1000 + ", " 1001 + isPartOfConference 1002 + ", " 1003 + addressType); 1004 } 1005 1006 if (mBluetoothHeadset == null) { 1007 Log.w( 1008 TAG, 1009 "mBluetoothHeasdset is null when sending clcc for BluetoothCall " 1010 + index 1011 + ", " 1012 + direction 1013 + ", " 1014 + state 1015 + ", " 1016 + isPartOfConference 1017 + ", " 1018 + addressType); 1019 } else { 1020 mBluetoothHeadset.clccResponse( 1021 index, direction, state, 0, isPartOfConference, address, addressType); 1022 } 1023 } 1024 getNextAvailableClccIndex(int index)1025 int getNextAvailableClccIndex(int index) { 1026 // find the next available smallest index 1027 SortedSet<Integer> availableIndex = new TreeSet<>(); 1028 for (int i = index; i <= mMaxNumberOfCalls + 1; i++) { 1029 availableIndex.add(i); 1030 } 1031 for (BluetoothCall bluetoothCall : mBluetoothCallHashMap.values()) { 1032 int callCLCCIndex = bluetoothCall.mClccIndex; 1033 if (availableIndex.contains(callCLCCIndex)) { 1034 availableIndex.remove(callCLCCIndex); 1035 } 1036 } 1037 Log.d(TAG, "availableIndex first: " + availableIndex.first()); 1038 return availableIndex.first(); 1039 } 1040 1041 /** 1042 * Returns the caches index for the specified call. If no such index exists, then an index is 1043 * given (the smallest number starting from 1 that isn't already taken). 1044 */ getIndexForCall(BluetoothCall call)1045 private int getIndexForCall(BluetoothCall call) { 1046 if (mCallInfo.isNullCall(call)) { 1047 Log.w(TAG, "empty or null call"); 1048 return -1; 1049 } 1050 if (call.mClccIndex >= 1) { 1051 return call.mClccIndex; 1052 } 1053 1054 int index = 1; // Indexes for bluetooth clcc are 1-based. 1055 if (call.isConference()) { 1056 index = mMaxNumberOfCalls + 1; // The conference call should have a higher index 1057 Log.i(TAG, "getIndexForCall for conference call starting from " + mMaxNumberOfCalls); 1058 } 1059 1060 // NOTE: Indexes are removed in {@link #onCallRemoved}. 1061 call.mClccIndex = getNextAvailableClccIndex(index); 1062 Log.d(TAG, "call " + call.getId() + " CLCC index is " + call.mClccIndex); 1063 return call.mClccIndex; 1064 } 1065 _processChld(int chld)1066 private boolean _processChld(int chld) { 1067 BluetoothCall activeCall = mCallInfo.getActiveCall(); 1068 BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall(); 1069 if (ringingCall == null) { 1070 Log.i(TAG, "ringingCall null at processChld"); 1071 } else { 1072 Log.i(TAG, "ringingCall hashcode: " + ringingCall.hashCode()); 1073 } 1074 1075 BluetoothCall heldCall = mCallInfo.getHeldCall(); 1076 1077 Log.i( 1078 TAG, 1079 "Active: " 1080 + activeCall 1081 + " Ringing: " 1082 + ringingCall 1083 + " Held: " 1084 + heldCall 1085 + " chld: " 1086 + chld); 1087 1088 if (chld == CHLD_TYPE_RELEASEHELD) { 1089 Log.i(TAG, "chld is CHLD_TYPE_RELEASEHELD"); 1090 if (!mCallInfo.isNullCall(ringingCall)) { 1091 Log.i(TAG, "reject ringing call " + ringingCall.hashCode()); 1092 ringingCall.reject(false, null); 1093 return true; 1094 } else if (!mCallInfo.isNullCall(heldCall)) { 1095 heldCall.disconnect(); 1096 return true; 1097 } 1098 } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) { 1099 if (mCallInfo.isNullCall(activeCall) 1100 && mCallInfo.isNullCall(ringingCall) 1101 && mCallInfo.isNullCall(heldCall)) { 1102 return false; 1103 } 1104 if (!mCallInfo.isNullCall(activeCall)) { 1105 BluetoothCall conferenceCall = getBluetoothCallById(activeCall.getParentId()); 1106 if (!mCallInfo.isNullCall(conferenceCall) 1107 && conferenceCall.getState() == Call.STATE_ACTIVE) { 1108 Log.i(TAG, "CHLD: disconnect conference call"); 1109 conferenceCall.disconnect(); 1110 } else { 1111 activeCall.disconnect(); 1112 } 1113 } 1114 if (!mCallInfo.isNullCall(ringingCall)) { 1115 ringingCall.answer(ringingCall.getVideoState()); 1116 } else if (!mCallInfo.isNullCall(heldCall)) { 1117 heldCall.unhold(); 1118 } 1119 return true; 1120 } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) { 1121 if (!mCallInfo.isNullCall(activeCall) 1122 && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) { 1123 activeCall.swapConference(); 1124 Log.i(TAG, "CDMA calls in conference swapped, updating headset"); 1125 updateHeadsetWithCallState(true /* force */); 1126 return true; 1127 } else if (!mCallInfo.isNullCall(ringingCall)) { 1128 ringingCall.answer(VideoProfile.STATE_AUDIO_ONLY); 1129 return true; 1130 } else if (!mCallInfo.isNullCall(heldCall)) { 1131 // CallsManager will hold any active calls when unhold() is called on a 1132 // currently-held call. 1133 heldCall.unhold(); 1134 return true; 1135 } else if (!mCallInfo.isNullCall(activeCall) 1136 && activeCall.can(Connection.CAPABILITY_HOLD)) { 1137 activeCall.hold(); 1138 return true; 1139 } 1140 } else if (chld == CHLD_TYPE_ADDHELDTOCONF) { 1141 if (!mCallInfo.isNullCall(activeCall)) { 1142 if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) { 1143 activeCall.mergeConference(); 1144 return true; 1145 } else { 1146 List<BluetoothCall> conferenceable = 1147 getBluetoothCallsByIds(activeCall.getConferenceableCalls()); 1148 if (!conferenceable.isEmpty()) { 1149 activeCall.conference(conferenceable.get(0)); 1150 return true; 1151 } 1152 } 1153 } 1154 } 1155 return false; 1156 } 1157 1158 /** 1159 * Sends an update of the current BluetoothCall state to the current Headset. 1160 * 1161 * @param force {@code true} if the headset state should be sent regardless if no changes to the 1162 * state have occurred, {@code false} if the state should only be sent if the state has 1163 * changed. 1164 */ updateHeadsetWithCallState(boolean force)1165 private void updateHeadsetWithCallState(boolean force) { 1166 BluetoothCall activeCall = mCallInfo.getActiveCall(); 1167 BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall(); 1168 BluetoothCall heldCall = mCallInfo.getHeldCall(); 1169 1170 int bluetoothCallState = getBluetoothCallStateForUpdate(); 1171 1172 String ringingAddress = null; 1173 int ringingAddressType = DEFAULT_RINGING_ADDRESS_TYPE; 1174 String ringingName = null; 1175 if (!mCallInfo.isNullCall(ringingCall) 1176 && ringingCall.getHandle() != null 1177 && !ringingCall.isSilentRingingRequested()) { 1178 ringingAddress = ringingCall.getHandle().getSchemeSpecificPart(); 1179 if (ringingAddress != null) { 1180 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress); 1181 } 1182 ringingName = ringingCall.getCallerDisplayName(); 1183 if (TextUtils.isEmpty(ringingName)) { 1184 ringingName = ringingCall.getContactDisplayName(); 1185 } 1186 } 1187 if (ringingAddress == null) { 1188 ringingAddress = ""; 1189 } 1190 1191 int numActiveCalls = mCallInfo.isNullCall(activeCall) ? 0 : 1; 1192 int numHeldCalls = mCallInfo.getNumHeldCalls(); 1193 int numChildrenOfActiveCall = 1194 mCallInfo.isNullCall(activeCall) ? 0 : activeCall.getChildrenIds().size(); 1195 1196 // Intermediate state for GSM calls which are in the process of being swapped. 1197 // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls 1198 // are held? 1199 boolean callsPendingSwitch = (numHeldCalls == 2); 1200 1201 // For conference calls which support swapping the active BluetoothCall within the 1202 // conference (namely CDMA calls) we need to expose that as a held BluetoothCall 1203 // in order for the BT device to show "swap" and "merge" functionality. 1204 boolean ignoreHeldCallChange = false; 1205 if (!mCallInfo.isNullCall(activeCall) 1206 && activeCall.isConference() 1207 && !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) { 1208 if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) { 1209 // Indicate that BT device should show SWAP command by indicating that there is a 1210 // BluetoothCall on hold, but only if the conference wasn't previously merged. 1211 numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1; 1212 } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) { 1213 numHeldCalls = 1; // Merge is available, so expose via numHeldCalls. 1214 } 1215 1216 for (Integer id : activeCall.getChildrenIds()) { 1217 // Held BluetoothCall has changed due to it being combined into a CDMA conference. 1218 // Keep track of this and ignore any future update since it doesn't really count 1219 // as a BluetoothCall change. 1220 if (mOldHeldCall != null && Objects.equals(mOldHeldCall.getId(), id)) { 1221 ignoreHeldCallChange = true; 1222 break; 1223 } 1224 } 1225 } 1226 1227 if (mBluetoothHeadset != null 1228 && (force 1229 || (!callsPendingSwitch 1230 && (numActiveCalls != mNumActiveCalls 1231 || numChildrenOfActiveCall != mNumChildrenOfActiveCall 1232 || numHeldCalls != mNumHeldCalls 1233 || bluetoothCallState != mBluetoothCallState 1234 || !TextUtils.equals(ringingAddress, mRingingAddress) 1235 || ringingAddressType != mRingingAddressType 1236 || (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) { 1237 1238 // If the BluetoothCall is transitioning into the alerting state, send DIALING first. 1239 // Some devices expect to see a DIALING state prior to seeing an ALERTING state 1240 // so we need to send it first. 1241 boolean sendDialingFirst = 1242 mBluetoothCallState != bluetoothCallState 1243 && bluetoothCallState == CALL_STATE_ALERTING; 1244 1245 mOldHeldCall = heldCall; 1246 mNumActiveCalls = numActiveCalls; 1247 mNumChildrenOfActiveCall = numChildrenOfActiveCall; 1248 mNumHeldCalls = numHeldCalls; 1249 mBluetoothCallState = bluetoothCallState; 1250 mRingingAddress = ringingAddress; 1251 mRingingAddressType = ringingAddressType; 1252 1253 if (sendDialingFirst) { 1254 // Log in full to make logs easier to debug. 1255 Log.i( 1256 TAG, 1257 "updateHeadsetWithCallState " 1258 + "numActive " 1259 + mNumActiveCalls 1260 + ", " 1261 + "numHeld " 1262 + mNumHeldCalls 1263 + ", " 1264 + "callState " 1265 + CALL_STATE_DIALING 1266 + ", " 1267 + "ringing type " 1268 + mRingingAddressType); 1269 mBluetoothHeadset.phoneStateChanged( 1270 mNumActiveCalls, 1271 mNumHeldCalls, 1272 CALL_STATE_DIALING, 1273 mRingingAddress, 1274 mRingingAddressType, 1275 ringingName); 1276 } 1277 1278 Log.i( 1279 TAG, 1280 "updateHeadsetWithCallState " 1281 + "numActive " 1282 + mNumActiveCalls 1283 + ", " 1284 + "numHeld " 1285 + mNumHeldCalls 1286 + ", " 1287 + "callState " 1288 + mBluetoothCallState 1289 + ", " 1290 + "ringing type " 1291 + mRingingAddressType); 1292 1293 mBluetoothHeadset.phoneStateChanged( 1294 mNumActiveCalls, 1295 mNumHeldCalls, 1296 mBluetoothCallState, 1297 mRingingAddress, 1298 mRingingAddressType, 1299 ringingName); 1300 1301 mHeadsetUpdatedRecently = true; 1302 } 1303 } 1304 getBluetoothCallStateForUpdate()1305 private int getBluetoothCallStateForUpdate() { 1306 BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall(); 1307 BluetoothCall dialingCall = mCallInfo.getOutgoingCall(); 1308 boolean hasOnlyDisconnectedCalls = mCallInfo.hasOnlyDisconnectedCalls(); 1309 1310 // 1311 // !! WARNING !! 1312 // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not 1313 // used in this version of the BluetoothCall state mappings. This is on purpose. 1314 // phone_state_change() in btif_hf.c is not written to handle these states. Only with the 1315 // listCalls*() method are WAITING and ACTIVE used. 1316 // Using the unsupported states here caused problems with inconsistent state in some 1317 // bluetooth devices (like not getting out of ringing state after answering a call). 1318 // 1319 int bluetoothCallState = CALL_STATE_IDLE; 1320 if (!mCallInfo.isNullCall(ringingCall) && !ringingCall.isSilentRingingRequested()) { 1321 bluetoothCallState = CALL_STATE_INCOMING; 1322 } else if (!mCallInfo.isNullCall(dialingCall)) { 1323 bluetoothCallState = CALL_STATE_ALERTING; 1324 } else if (hasOnlyDisconnectedCalls || mIsDisconnectedTonePlaying) { 1325 // Keep the DISCONNECTED state until the disconnect tone's playback is done 1326 bluetoothCallState = CALL_STATE_DISCONNECTED; 1327 } 1328 return bluetoothCallState; 1329 } 1330 getBtCallState(BluetoothCall call, boolean isForeground)1331 private int getBtCallState(BluetoothCall call, boolean isForeground) { 1332 switch (call.getState()) { 1333 case Call.STATE_NEW: 1334 case Call.STATE_DISCONNECTED: 1335 case Call.STATE_AUDIO_PROCESSING: 1336 return CALL_STATE_IDLE; 1337 1338 case Call.STATE_ACTIVE: 1339 return CALL_STATE_ACTIVE; 1340 1341 case Call.STATE_CONNECTING: 1342 case Call.STATE_SELECT_PHONE_ACCOUNT: 1343 case Call.STATE_DIALING: 1344 case Call.STATE_PULLING_CALL: 1345 // Yes, this is correctly returning ALERTING. 1346 // "Dialing" for BT means that we have sent information to the service provider 1347 // to place the BluetoothCall but there is no confirmation that the BluetoothCall 1348 // is going through. When there finally is confirmation, the ringback is 1349 // played which is referred to as an "alert" tone, thus, ALERTING. 1350 // TODO: We should consider using the ALERTING terms in Telecom because that 1351 // seems to be more industry-standard. 1352 return CALL_STATE_ALERTING; 1353 1354 case Call.STATE_HOLDING: 1355 return CALL_STATE_HELD; 1356 1357 case Call.STATE_RINGING: 1358 case Call.STATE_SIMULATED_RINGING: 1359 if (call.isSilentRingingRequested()) { 1360 return CALL_STATE_IDLE; 1361 } else if (isForeground) { 1362 return CALL_STATE_INCOMING; 1363 } else { 1364 return CALL_STATE_WAITING; 1365 } 1366 } 1367 return CALL_STATE_IDLE; 1368 } 1369 1370 @VisibleForTesting getCallback(BluetoothCall call)1371 public CallStateCallback getCallback(BluetoothCall call) { 1372 return mCallbacks.get(call.getId()); 1373 } 1374 1375 @VisibleForTesting setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset)1376 public void setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset) { 1377 mBluetoothHeadset = bluetoothHeadset; 1378 } 1379 1380 @VisibleForTesting getBluetoothCallById(Integer id)1381 public BluetoothCall getBluetoothCallById(Integer id) { 1382 if (mBluetoothCallHashMap.containsKey(id)) { 1383 return mBluetoothCallHashMap.get(id); 1384 } 1385 return null; 1386 } 1387 1388 @VisibleForTesting getBluetoothCallsByIds(List<Integer> ids)1389 public List<BluetoothCall> getBluetoothCallsByIds(List<Integer> ids) { 1390 List<BluetoothCall> calls = new ArrayList<>(); 1391 for (Integer id : ids) { 1392 BluetoothCall call = getBluetoothCallById(id); 1393 if (!mCallInfo.isNullCall(call)) { 1394 calls.add(call); 1395 } 1396 } 1397 return calls; 1398 } 1399 1400 // extract call information functions out into this part, so we can mock it in testing 1401 @VisibleForTesting 1402 public class CallInfo { 1403 getForegroundCall()1404 public BluetoothCall getForegroundCall() { 1405 LinkedHashSet<Integer> states = new LinkedHashSet<Integer>(); 1406 BluetoothCall foregroundCall; 1407 1408 states.add(Call.STATE_CONNECTING); 1409 foregroundCall = getCallByStates(states); 1410 if (!mCallInfo.isNullCall(foregroundCall)) { 1411 return foregroundCall; 1412 } 1413 1414 states.clear(); 1415 states.add(Call.STATE_ACTIVE); 1416 states.add(Call.STATE_DIALING); 1417 states.add(Call.STATE_PULLING_CALL); 1418 foregroundCall = getCallByStates(states); 1419 if (!mCallInfo.isNullCall(foregroundCall)) { 1420 return foregroundCall; 1421 } 1422 1423 states.clear(); 1424 states.add(Call.STATE_RINGING); 1425 foregroundCall = getCallByStates(states); 1426 if (!mCallInfo.isNullCall(foregroundCall)) { 1427 return foregroundCall; 1428 } 1429 1430 return null; 1431 } 1432 getCallByStates(LinkedHashSet<Integer> states)1433 public BluetoothCall getCallByStates(LinkedHashSet<Integer> states) { 1434 List<BluetoothCall> calls = getBluetoothCalls(); 1435 for (BluetoothCall call : calls) { 1436 if (states.contains(call.getState())) { 1437 return call; 1438 } 1439 } 1440 return null; 1441 } 1442 getCallByState(int state)1443 public BluetoothCall getCallByState(int state) { 1444 List<BluetoothCall> calls = getBluetoothCalls(); 1445 for (BluetoothCall call : calls) { 1446 if (state == call.getState()) { 1447 return call; 1448 } 1449 } 1450 return null; 1451 } 1452 getNumHeldCalls()1453 public int getNumHeldCalls() { 1454 int number = 0; 1455 List<BluetoothCall> calls = getBluetoothCalls(); 1456 for (BluetoothCall call : calls) { 1457 if (call.getState() == Call.STATE_HOLDING) { 1458 number++; 1459 } 1460 } 1461 return number; 1462 } 1463 hasOnlyDisconnectedCalls()1464 public boolean hasOnlyDisconnectedCalls() { 1465 List<BluetoothCall> calls = getBluetoothCalls(); 1466 if (calls.size() == 0) { 1467 return false; 1468 } 1469 for (BluetoothCall call : calls) { 1470 if (call.getState() != Call.STATE_DISCONNECTED) { 1471 return false; 1472 } 1473 } 1474 return true; 1475 } 1476 getBluetoothCalls()1477 public List<BluetoothCall> getBluetoothCalls() { 1478 return getBluetoothCallsByIds(BluetoothCall.getIds(getCalls())); 1479 } 1480 getOutgoingCall()1481 public BluetoothCall getOutgoingCall() { 1482 LinkedHashSet<Integer> states = new LinkedHashSet<Integer>(); 1483 states.add(Call.STATE_CONNECTING); 1484 states.add(Call.STATE_DIALING); 1485 states.add(Call.STATE_PULLING_CALL); 1486 return getCallByStates(states); 1487 } 1488 getRingingOrSimulatedRingingCall()1489 public BluetoothCall getRingingOrSimulatedRingingCall() { 1490 LinkedHashSet<Integer> states = new LinkedHashSet<Integer>(); 1491 states.add(Call.STATE_RINGING); 1492 states.add(Call.STATE_SIMULATED_RINGING); 1493 return getCallByStates(states); 1494 } 1495 getActiveCall()1496 public BluetoothCall getActiveCall() { 1497 return getCallByState(Call.STATE_ACTIVE); 1498 } 1499 getHeldCall()1500 public BluetoothCall getHeldCall() { 1501 return getCallByState(Call.STATE_HOLDING); 1502 } 1503 1504 /** 1505 * Returns the best phone account to use for the given state of all calls. First, tries to 1506 * return the phone account for the foreground call, second the default phone account for 1507 * PhoneAccount.SCHEME_TEL. 1508 */ getBestPhoneAccount()1509 public PhoneAccount getBestPhoneAccount() { 1510 BluetoothCall call = getForegroundCall(); 1511 1512 PhoneAccount account = null; 1513 if (!mCallInfo.isNullCall(call)) { 1514 PhoneAccountHandle handle = call.getAccountHandle(); 1515 if (handle != null) { 1516 // First try to get the network name of the foreground call. 1517 account = mTelecomManager.getPhoneAccount(handle); 1518 } 1519 } 1520 1521 if (account == null) { 1522 // Second, Try to get the label for the default Phone Account. 1523 if (mTelecomManager == null) { 1524 Log.w(TAG, "mTelecomManager is null"); 1525 return null; 1526 } 1527 List<PhoneAccountHandle> handles = 1528 mTelecomManager.getPhoneAccountsSupportingScheme(PhoneAccount.SCHEME_TEL); 1529 while (handles.iterator().hasNext()) { 1530 account = mTelecomManager.getPhoneAccount(handles.iterator().next()); 1531 if (account != null) { 1532 return account; 1533 } 1534 } 1535 } 1536 return null; 1537 } 1538 isNullCall(BluetoothCall call)1539 public boolean isNullCall(BluetoothCall call) { 1540 return call == null || call.isCallNull(); 1541 } 1542 getCallByCallId(UUID callId)1543 public BluetoothCall getCallByCallId(UUID callId) { 1544 List<BluetoothCall> calls = getBluetoothCalls(); 1545 for (BluetoothCall call : calls) { 1546 Log.i(TAG, "getCallByCallId lookingFor=" + callId + " has=" + call.getTbsCallId()); 1547 if (callId.equals(call.getTbsCallId())) { 1548 return call; 1549 } 1550 } 1551 return null; 1552 } 1553 } 1554 ; 1555 1556 @VisibleForTesting setBluetoothLeCallControl(BluetoothLeCallControlProxy bluetoothTbs)1557 public void setBluetoothLeCallControl(BluetoothLeCallControlProxy bluetoothTbs) { 1558 mBluetoothLeCallControl = bluetoothTbs; 1559 1560 if ((mBluetoothLeCallControl) != null && (mTelecomManager != null)) { 1561 mBluetoothLeCallControl.registerBearer( 1562 TAG, 1563 new ArrayList<String>(Arrays.asList("tel")), 1564 BluetoothLeCallControl.CAPABILITY_HOLD_CALL, 1565 getNetworkOperator(), 1566 getBearerTechnology(), 1567 mExecutor, 1568 mBluetoothLeCallControlCallback); 1569 } 1570 } 1571 getTbsCallState(BluetoothCall call)1572 private Integer getTbsCallState(BluetoothCall call) { 1573 switch (call.getState()) { 1574 case Call.STATE_ACTIVE: 1575 return BluetoothLeCall.STATE_ACTIVE; 1576 1577 case Call.STATE_CONNECTING: 1578 case Call.STATE_SELECT_PHONE_ACCOUNT: 1579 return BluetoothLeCall.STATE_DIALING; 1580 1581 case Call.STATE_DIALING: 1582 case Call.STATE_PULLING_CALL: 1583 return BluetoothLeCall.STATE_ALERTING; 1584 1585 case Call.STATE_HOLDING: 1586 return BluetoothLeCall.STATE_LOCALLY_HELD; 1587 1588 case Call.STATE_RINGING: 1589 case Call.STATE_SIMULATED_RINGING: 1590 if (call.isSilentRingingRequested()) { 1591 return null; 1592 } else { 1593 return BluetoothLeCall.STATE_INCOMING; 1594 } 1595 } 1596 return null; 1597 } 1598 1599 @VisibleForTesting getTbsTerminationReason(BluetoothCall call)1600 int getTbsTerminationReason(BluetoothCall call) { 1601 DisconnectCause cause = call.getDisconnectCause(); 1602 if (cause == null) { 1603 Log.w(TAG, " termination cause is null"); 1604 return BluetoothLeCallControl.TERMINATION_REASON_FAIL; 1605 } 1606 1607 switch (cause.getCode()) { 1608 case DisconnectCause.BUSY: 1609 return BluetoothLeCallControl.TERMINATION_REASON_LINE_BUSY; 1610 case DisconnectCause.REMOTE: 1611 case DisconnectCause.REJECTED: 1612 return BluetoothLeCallControl.TERMINATION_REASON_REMOTE_HANGUP; 1613 case DisconnectCause.LOCAL: 1614 if (mIsTerminatedByClient) { 1615 mIsTerminatedByClient = false; 1616 return BluetoothLeCallControl.TERMINATION_REASON_CLIENT_HANGUP; 1617 } 1618 return BluetoothLeCallControl.TERMINATION_REASON_SERVER_HANGUP; 1619 case DisconnectCause.ERROR: 1620 return BluetoothLeCallControl.TERMINATION_REASON_NETWORK_CONGESTION; 1621 case DisconnectCause.CONNECTION_MANAGER_NOT_SUPPORTED: 1622 return BluetoothLeCallControl.TERMINATION_REASON_INVALID_URI; 1623 default: 1624 return BluetoothLeCallControl.TERMINATION_REASON_FAIL; 1625 } 1626 } 1627 createTbsCall(BluetoothCall call)1628 private BluetoothLeCall createTbsCall(BluetoothCall call) { 1629 Integer state = getTbsCallState(call); 1630 boolean isConferenceWithNoChildren = isConferenceWithNoChildren(call); 1631 1632 if (state == null) { 1633 return null; 1634 } 1635 1636 BluetoothCall conferenceCall = getBluetoothCallById(call.getParentId()); 1637 if (!mCallInfo.isNullCall(conferenceCall)) { 1638 // Run some alternative states for Conference-level merge/swap support. 1639 // Basically, if BluetoothCall supports swapping or merging at the 1640 // conference-level, 1641 // then we need to expose the calls as having distinct states 1642 // (ACTIVE vs CAPABILITY_HOLD) or 1643 // the functionality won't show up on the bluetooth device. 1644 1645 // Before doing any special logic, ensure that we are dealing with an 1646 // ACTIVE BluetoothCall and that the conference itself has a notion of 1647 // the current "active" child call. 1648 BluetoothCall activeChild = 1649 getBluetoothCallById(conferenceCall.getGenericConferenceActiveChildCallId()); 1650 if (state == BluetoothLeCall.STATE_ACTIVE && !mCallInfo.isNullCall(activeChild)) { 1651 // Reevaluate state if we can MERGE or if we can SWAP without previously having 1652 // MERGED. 1653 boolean shouldReevaluateState = 1654 conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) 1655 || (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) 1656 && !conferenceCall.wasConferencePreviouslyMerged()); 1657 1658 if (shouldReevaluateState) { 1659 if (call == activeChild) { 1660 state = BluetoothLeCall.STATE_ACTIVE; 1661 } else { 1662 // At this point we know there is an "active" child and we know that it is 1663 // not this call, so set it to HELD instead. 1664 state = BluetoothLeCall.STATE_LOCALLY_HELD; 1665 } 1666 } 1667 } 1668 if (conferenceCall.getState() == Call.STATE_HOLDING 1669 && conferenceCall.can(Connection.CAPABILITY_MANAGE_CONFERENCE)) { 1670 // If the parent IMS CEP conference BluetoothCall is on hold, we should mark 1671 // this BluetoothCall as being on hold regardless of what the other 1672 // children are doing. 1673 state = BluetoothLeCall.STATE_LOCALLY_HELD; 1674 } 1675 } else if (isConferenceWithNoChildren) { 1676 // Handle the special case of an IMS conference BluetoothCall without conference 1677 // event package support. 1678 // The BluetoothCall will be marked as a conference, but the conference will not 1679 // have 1680 // child calls where conference event packages are not used by the carrier. 1681 } 1682 1683 final Uri addressUri; 1684 if (call.getGatewayInfo() != null) { 1685 addressUri = call.getGatewayInfo().getOriginalAddress(); 1686 } else { 1687 addressUri = call.getHandle(); 1688 } 1689 1690 String uri = addressUri == null ? null : addressUri.toString(); 1691 int callFlags = call.isIncoming() ? 0 : BluetoothLeCall.FLAG_OUTGOING_CALL; 1692 1693 String friendlyName = call.getCallerDisplayName(); 1694 if (TextUtils.isEmpty(friendlyName)) { 1695 friendlyName = call.getContactDisplayName(); 1696 } 1697 1698 return new BluetoothLeCall(call.getTbsCallId(), uri, friendlyName, state, callFlags); 1699 } 1700 sendTbsCurrentCallsList()1701 private void sendTbsCurrentCallsList() { 1702 List<BluetoothLeCall> tbsCalls = new ArrayList<>(); 1703 1704 for (BluetoothCall call : mBluetoothCallHashMap.values()) { 1705 BluetoothLeCall tbsCall = createTbsCall(call); 1706 if (tbsCall != null) { 1707 tbsCalls.add(tbsCall); 1708 } 1709 } 1710 1711 mBluetoothLeCallControl.currentCallsList(tbsCalls); 1712 } 1713 1714 @VisibleForTesting 1715 final BluetoothLeCallControl.Callback mBluetoothLeCallControlCallback = 1716 new BluetoothLeCallControl.Callback() { 1717 1718 @Override 1719 public void onAcceptCall(int requestId, UUID callId) { 1720 synchronized (LOCK) { 1721 enforceModifyPermission(); 1722 Log.i(TAG, "TBS - accept call=" + callId); 1723 int result = BluetoothLeCallControl.RESULT_SUCCESS; 1724 BluetoothCall call = mCallInfo.getCallByCallId(callId); 1725 if (mCallInfo.isNullCall(call)) { 1726 result = BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID; 1727 } else { 1728 call.answer(VideoProfile.STATE_AUDIO_ONLY); 1729 } 1730 mBluetoothLeCallControl.requestResult(requestId, result); 1731 } 1732 } 1733 1734 @Override 1735 public void onTerminateCall(int requestId, UUID callId) { 1736 synchronized (LOCK) { 1737 enforceModifyPermission(); 1738 Log.i(TAG, "TBS - terminate call=" + callId); 1739 int result = BluetoothLeCallControl.RESULT_SUCCESS; 1740 BluetoothCall call = mCallInfo.getCallByCallId(callId); 1741 if (mCallInfo.isNullCall(call)) { 1742 result = BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID; 1743 } else { 1744 mIsTerminatedByClient = true; 1745 call.disconnect(); 1746 } 1747 mBluetoothLeCallControl.requestResult(requestId, result); 1748 } 1749 } 1750 1751 @Override 1752 public void onHoldCall(int requestId, UUID callId) { 1753 synchronized (LOCK) { 1754 enforceModifyPermission(); 1755 Log.i(TAG, "TBS - hold call=" + callId); 1756 int result = BluetoothLeCallControl.RESULT_SUCCESS; 1757 BluetoothCall call = mCallInfo.getCallByCallId(callId); 1758 if (mCallInfo.isNullCall(call)) { 1759 result = BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID; 1760 } else { 1761 call.hold(); 1762 } 1763 mBluetoothLeCallControl.requestResult(requestId, result); 1764 } 1765 } 1766 1767 @Override 1768 public void onUnholdCall(int requestId, UUID callId) { 1769 synchronized (LOCK) { 1770 enforceModifyPermission(); 1771 Log.i(TAG, "TBS - unhold call=" + callId); 1772 int result = BluetoothLeCallControl.RESULT_SUCCESS; 1773 BluetoothCall call = mCallInfo.getCallByCallId(callId); 1774 if (mCallInfo.isNullCall(call)) { 1775 result = BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID; 1776 } else { 1777 call.unhold(); 1778 } 1779 mBluetoothLeCallControl.requestResult(requestId, result); 1780 } 1781 } 1782 1783 @Override 1784 public void onPlaceCall(int requestId, UUID callId, String uri) { 1785 mBluetoothLeCallControl.requestResult( 1786 requestId, BluetoothLeCallControl.RESULT_ERROR_APPLICATION); 1787 } 1788 1789 @Override 1790 public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) { 1791 synchronized (LOCK) { 1792 Log.i(TAG, "TBS - onJoinCalls"); 1793 int result = BluetoothLeCallControl.RESULT_SUCCESS; 1794 List<UUID> alreadyJoinedCalls = new ArrayList<>(); 1795 BluetoothCall baseCallInstance = null; 1796 1797 if (callIds.size() < 2) { 1798 Log.e( 1799 TAG, 1800 "TBS - onJoinCalls, join call number is invalid: " 1801 + callIds.size()); 1802 result = BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID; 1803 mBluetoothLeCallControl.requestResult(requestId, result); 1804 return; 1805 } 1806 1807 for (UUID callToJoinUuid : callIds) { 1808 BluetoothCall callToJoinInstance = 1809 mCallInfo.getCallByCallId(callToJoinUuid); 1810 1811 /* Skip invalid and already add device */ 1812 if ((callToJoinInstance == null) 1813 || (alreadyJoinedCalls.contains(callToJoinUuid))) { 1814 continue; 1815 } 1816 1817 /* Lets make first valid call the base call */ 1818 if (baseCallInstance == null) { 1819 baseCallInstance = callToJoinInstance; 1820 alreadyJoinedCalls.add(callToJoinUuid); 1821 continue; 1822 } 1823 1824 baseCallInstance.conference(callToJoinInstance); 1825 alreadyJoinedCalls.add(callToJoinUuid); 1826 } 1827 1828 if ((baseCallInstance == null) || (alreadyJoinedCalls.size() < 2)) { 1829 result = BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID; 1830 } 1831 1832 mBluetoothLeCallControl.requestResult(requestId, result); 1833 } 1834 } 1835 }; 1836 } 1837