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