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