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