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