1 /* 2 * Copyright 2021 HIMSA II K/S - www.himsa.com. 3 * Represented by EHIMA - www.ehima.com 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.bluetooth.tbs; 19 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothLeAudio; 22 import android.bluetooth.BluetoothLeCall; 23 import android.bluetooth.BluetoothLeCallControl; 24 import android.bluetooth.IBluetoothLeCallControlCallback; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.media.AudioManager; 30 import android.net.Uri; 31 import android.os.ParcelUuid; 32 import android.os.RemoteException; 33 import android.util.Log; 34 35 import com.android.bluetooth.btservice.ServiceFactory; 36 import com.android.bluetooth.le_audio.ContentControlIdKeeper; 37 import com.android.bluetooth.le_audio.LeAudioService; 38 import com.android.internal.annotations.VisibleForTesting; 39 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.HashMap; 43 import java.util.HashSet; 44 import java.util.LinkedHashSet; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.TreeMap; 48 import java.util.UUID; 49 50 /** Container class to store TBS instances */ 51 public class TbsGeneric { 52 53 private static final String TAG = "TbsGeneric"; 54 55 private static final String UCI = "GTBS"; 56 private static final String DEFAULT_PROVIDER_NAME = "none"; 57 /* Use GSM as default technology value. It is used only 58 * when bearer is not registered. It will be updated on the phone call 59 */ 60 private static final int DEFAULT_BEARER_TECHNOLOGY = 61 BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_GSM; 62 private static final String UNKNOWN_FRIENDLY_NAME = "unknown"; 63 64 /** Class representing the pending request sent to the application */ 65 private static class Request { 66 BluetoothDevice device; 67 List<UUID> callIdList; 68 int requestedOpcode; 69 int callIndex; 70 Request(BluetoothDevice device, UUID callId, int requestedOpcode, int callIndex)71 public Request(BluetoothDevice device, UUID callId, int requestedOpcode, int callIndex) { 72 this.device = device; 73 this.callIdList = Arrays.asList(callId); 74 this.requestedOpcode = requestedOpcode; 75 this.callIndex = callIndex; 76 } 77 Request( BluetoothDevice device, List<ParcelUuid> callIds, int requestedOpcode, int callIndex)78 public Request( 79 BluetoothDevice device, 80 List<ParcelUuid> callIds, 81 int requestedOpcode, 82 int callIndex) { 83 this.device = device; 84 this.callIdList = new ArrayList<>(); 85 for (ParcelUuid callId : callIds) { 86 this.callIdList.add(callId.getUuid()); 87 } 88 this.requestedOpcode = requestedOpcode; 89 this.callIndex = callIndex; 90 } 91 } 92 93 /* Application-registered TBS instance */ 94 private static class Bearer { 95 final String token; 96 final IBluetoothLeCallControlCallback callback; 97 List<String> uriSchemes; 98 final int capabilities; 99 final int ccid; 100 String providerName; 101 int technology; 102 Map<UUID, Integer> callIdIndexMap = new HashMap<>(); 103 Map<Integer, Request> requestMap = new HashMap<>(); 104 Bearer( String token, IBluetoothLeCallControlCallback callback, List<String> uriSchemes, int capabilities, String providerName, int technology, int ccid)105 Bearer( 106 String token, 107 IBluetoothLeCallControlCallback callback, 108 List<String> uriSchemes, 109 int capabilities, 110 String providerName, 111 int technology, 112 int ccid) { 113 this.token = token; 114 this.callback = callback; 115 this.uriSchemes = uriSchemes; 116 this.capabilities = capabilities; 117 this.providerName = providerName; 118 this.technology = technology; 119 this.ccid = ccid; 120 } 121 } 122 123 private boolean mIsInitialized = false; 124 private TbsGatt mTbsGatt = null; 125 private List<Bearer> mBearerList = new ArrayList<>(); 126 private int mLastIndexAssigned = TbsCall.INDEX_UNASSIGNED; 127 private Map<Integer, TbsCall> mCurrentCallsList = new TreeMap<>(); 128 private Bearer mForegroundBearer = null; 129 private int mLastRequestIdAssigned = 0; 130 private List<String> mUriSchemes = new ArrayList<>(Arrays.asList("tel")); 131 private Receiver mReceiver = null; 132 private int mStoredRingerMode = -1; 133 private final ServiceFactory mFactory = new ServiceFactory(); 134 private LeAudioService mLeAudioService; 135 136 private final class Receiver extends BroadcastReceiver { 137 @Override onReceive(Context context, Intent intent)138 public void onReceive(Context context, Intent intent) { 139 synchronized (TbsGeneric.this) { 140 if (!mIsInitialized) { 141 Log.w(TAG, "onReceive called while not initialized."); 142 return; 143 } 144 145 final String action = intent.getAction(); 146 if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { 147 int ringerMode = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1); 148 149 if (ringerMode < 0 || ringerMode == mStoredRingerMode) return; 150 151 mStoredRingerMode = ringerMode; 152 153 if (isSilentModeEnabled()) { 154 mTbsGatt.setSilentModeFlag(); 155 } else { 156 mTbsGatt.clearSilentModeFlag(); 157 } 158 } 159 } 160 } 161 } 162 ; 163 init(TbsGatt tbsGatt)164 public synchronized boolean init(TbsGatt tbsGatt) { 165 Log.d(TAG, "init"); 166 mTbsGatt = tbsGatt; 167 168 int ccid = 169 ContentControlIdKeeper.acquireCcid( 170 new ParcelUuid(TbsGatt.UUID_GTBS), 171 BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL); 172 if (!isCcidValid(ccid)) { 173 Log.e(TAG, " CCID is not valid"); 174 cleanup(); 175 return false; 176 } 177 178 if (!mTbsGatt.init( 179 ccid, 180 UCI, 181 mUriSchemes, 182 true, 183 true, 184 DEFAULT_PROVIDER_NAME, 185 DEFAULT_BEARER_TECHNOLOGY, 186 mTbsGattCallback)) { 187 Log.e(TAG, " TbsGatt init failed"); 188 cleanup(); 189 return false; 190 } 191 192 AudioManager audioManager = mTbsGatt.getContext().getSystemService(AudioManager.class); 193 if (audioManager == null) { 194 Log.w(TAG, " AudioManager is not available"); 195 cleanup(); 196 return false; 197 } 198 199 // read initial value of ringer mode 200 mStoredRingerMode = audioManager.getRingerMode(); 201 202 if (isSilentModeEnabled()) { 203 mTbsGatt.setSilentModeFlag(); 204 } else { 205 mTbsGatt.clearSilentModeFlag(); 206 } 207 208 mReceiver = new Receiver(); 209 IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); 210 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 211 mTbsGatt.getContext().registerReceiver(mReceiver, filter); 212 213 mIsInitialized = true; 214 return true; 215 } 216 cleanup()217 public synchronized void cleanup() { 218 Log.d(TAG, "cleanup"); 219 220 if (mTbsGatt != null) { 221 if (mReceiver != null) { 222 mTbsGatt.getContext().unregisterReceiver(mReceiver); 223 } 224 mTbsGatt.cleanup(); 225 mTbsGatt = null; 226 } 227 228 mIsInitialized = false; 229 } 230 231 /** 232 * Inform TBS GATT instance about authorization change for device. 233 * 234 * @param device device for which authorization is changed 235 */ onDeviceAuthorizationSet(BluetoothDevice device)236 public void onDeviceAuthorizationSet(BluetoothDevice device) { 237 // Notify TBS GATT service instance in case of pending operations 238 if (mTbsGatt != null) { 239 mTbsGatt.onDeviceAuthorizationSet(device); 240 } 241 } 242 243 /** 244 * Set inband ringtone for the device. When set, notification will be sent to given device. 245 * 246 * @param device device for which inband ringtone has been set 247 */ setInbandRingtoneSupport(BluetoothDevice device)248 public synchronized void setInbandRingtoneSupport(BluetoothDevice device) { 249 if (mTbsGatt == null) { 250 Log.w(TAG, "setInbandRingtoneSupport, mTbsGatt is null"); 251 return; 252 } 253 mTbsGatt.setInbandRingtoneFlag(device); 254 } 255 256 /** 257 * Clear inband ringtone for the device. When set, notification will be sent to given device. 258 * 259 * @param device device for which inband ringtone has been cleared 260 */ clearInbandRingtoneSupport(BluetoothDevice device)261 public synchronized void clearInbandRingtoneSupport(BluetoothDevice device) { 262 if (mTbsGatt == null) { 263 Log.w(TAG, "setInbandRingtoneSupport, mTbsGatt is null"); 264 return; 265 } 266 mTbsGatt.clearInbandRingtoneFlag(device); 267 } 268 isSilentModeEnabled()269 private synchronized boolean isSilentModeEnabled() { 270 return mStoredRingerMode != AudioManager.RINGER_MODE_NORMAL; 271 } 272 getBearerByToken(String token)273 private synchronized Bearer getBearerByToken(String token) { 274 for (Bearer bearer : mBearerList) { 275 if (bearer.token.equals(token)) { 276 return bearer; 277 } 278 } 279 return null; 280 } 281 getBearerByCcid(int ccid)282 private synchronized Bearer getBearerByCcid(int ccid) { 283 for (Bearer bearer : mBearerList) { 284 if (bearer.ccid == ccid) { 285 return bearer; 286 } 287 } 288 return null; 289 } 290 getBearerSupportingUri(String uri)291 private synchronized Bearer getBearerSupportingUri(String uri) { 292 for (Bearer bearer : mBearerList) { 293 for (String s : bearer.uriSchemes) { 294 if (uri.startsWith(s + ":")) { 295 return bearer; 296 } 297 } 298 } 299 return null; 300 } 301 getCallIdByIndex(int callIndex)302 private synchronized Map.Entry<UUID, Bearer> getCallIdByIndex(int callIndex) { 303 for (Bearer bearer : mBearerList) { 304 for (Map.Entry<UUID, Integer> callIdToIndex : bearer.callIdIndexMap.entrySet()) { 305 if (callIndex == callIdToIndex.getValue()) { 306 return Map.entry(callIdToIndex.getKey(), bearer); 307 } 308 } 309 } 310 return null; 311 } 312 addBearer( String token, IBluetoothLeCallControlCallback callback, String uci, List<String> uriSchemes, int capabilities, String providerName, int technology)313 public synchronized boolean addBearer( 314 String token, 315 IBluetoothLeCallControlCallback callback, 316 String uci, 317 List<String> uriSchemes, 318 int capabilities, 319 String providerName, 320 int technology) { 321 Log.d( 322 TAG, 323 "addBearer: token=" 324 + token 325 + " uci=" 326 + uci 327 + " uriSchemes=" 328 + uriSchemes 329 + " capabilities=" 330 + capabilities 331 + " providerName=" 332 + providerName 333 + " technology=" 334 + technology); 335 if (!mIsInitialized) { 336 Log.w(TAG, "addBearer called while not initialized."); 337 return false; 338 } 339 340 if (getBearerByToken(token) != null) { 341 Log.w(TAG, "addBearer: token=" + token + " registered already"); 342 return false; 343 } 344 345 // Acquire CCID for TbsObject. The CCID is released on remove() 346 Bearer bearer = 347 new Bearer( 348 token, 349 callback, 350 uriSchemes, 351 capabilities, 352 providerName, 353 technology, 354 ContentControlIdKeeper.acquireCcid( 355 new ParcelUuid(UUID.randomUUID()), 356 BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL)); 357 if (isCcidValid(bearer.ccid)) { 358 mBearerList.add(bearer); 359 360 updateUriSchemesSupported(); 361 if (mForegroundBearer == null) { 362 setForegroundBearer(bearer); 363 } 364 } else { 365 Log.e(TAG, "Failed to acquire ccid"); 366 } 367 368 if (callback != null) { 369 try { 370 Log.d(TAG, "ccid=" + bearer.ccid); 371 callback.onBearerRegistered(bearer.ccid); 372 } catch (RemoteException e) { 373 e.printStackTrace(); 374 } 375 } 376 377 return isCcidValid(bearer.ccid); 378 } 379 removeBearer(String token)380 public synchronized void removeBearer(String token) { 381 Log.d(TAG, "removeBearer: token=" + token); 382 383 if (!mIsInitialized) { 384 Log.w(TAG, "removeBearer called while not initialized."); 385 return; 386 } 387 388 Bearer bearer = getBearerByToken(token); 389 if (bearer == null) { 390 return; 391 } 392 393 // Remove the calls associated with this bearer 394 for (Integer callIndex : bearer.callIdIndexMap.values()) { 395 mCurrentCallsList.remove(callIndex); 396 } 397 398 if (bearer.callIdIndexMap.size() > 0) { 399 notifyCclc(); 400 } 401 402 // Release the ccid acquired 403 ContentControlIdKeeper.releaseCcid(bearer.ccid); 404 405 mBearerList.remove(bearer); 406 407 updateUriSchemesSupported(); 408 if (mForegroundBearer == bearer) { 409 setForegroundBearer(findNewForegroundBearer()); 410 } 411 } 412 checkRequestComplete(Bearer bearer, UUID callId, TbsCall tbsCall)413 private synchronized void checkRequestComplete(Bearer bearer, UUID callId, TbsCall tbsCall) { 414 // check if there's any pending request related to this call 415 Map.Entry<Integer, Request> requestEntry = null; 416 if (bearer.requestMap.size() > 0) { 417 for (Map.Entry<Integer, Request> entry : bearer.requestMap.entrySet()) { 418 if (entry.getValue().callIdList.contains(callId)) { 419 requestEntry = entry; 420 } 421 } 422 } 423 424 if (requestEntry == null) { 425 Log.d(TAG, "requestEntry is null"); 426 return; 427 } 428 429 int requestId = requestEntry.getKey(); 430 Request request = requestEntry.getValue(); 431 432 int result; 433 if (request.requestedOpcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE) { 434 if (mCurrentCallsList.get(request.callIndex) == null) { 435 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS; 436 } else { 437 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE; 438 } 439 } else if (request.requestedOpcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT) { 440 if (tbsCall.getState() != BluetoothLeCall.STATE_INCOMING) { 441 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS; 442 } else { 443 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE; 444 } 445 } else if (request.requestedOpcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD) { 446 if (tbsCall.getState() == BluetoothLeCall.STATE_LOCALLY_HELD 447 || tbsCall.getState() == BluetoothLeCall.STATE_LOCALLY_AND_REMOTELY_HELD) { 448 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS; 449 } else { 450 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE; 451 } 452 } else if (request.requestedOpcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE) { 453 if (tbsCall.getState() != BluetoothLeCall.STATE_LOCALLY_HELD 454 && tbsCall.getState() != BluetoothLeCall.STATE_LOCALLY_AND_REMOTELY_HELD) { 455 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS; 456 } else { 457 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE; 458 } 459 } else if (request.requestedOpcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_ORIGINATE) { 460 if (bearer.callIdIndexMap.get(request.callIdList.get(0)) != null) { 461 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS; 462 } else { 463 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE; 464 } 465 } else if (request.requestedOpcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_JOIN) { 466 /* While joining calls, those that are not in remotely held state should go to active */ 467 if (bearer.callIdIndexMap.get(callId) == null 468 || (tbsCall.getState() != BluetoothLeCall.STATE_ACTIVE 469 && tbsCall.getState() != BluetoothLeCall.STATE_REMOTELY_HELD)) { 470 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE; 471 } else { 472 /* Check if all of the pending calls transit to required state */ 473 for (UUID pendingCallId : request.callIdList) { 474 Integer callIndex = bearer.callIdIndexMap.get(pendingCallId); 475 TbsCall pendingTbsCall = mCurrentCallsList.get(callIndex); 476 if (pendingTbsCall.getState() != BluetoothLeCall.STATE_ACTIVE 477 && pendingTbsCall.getState() != BluetoothLeCall.STATE_REMOTELY_HELD) { 478 /* Still waiting for more call state updates */ 479 return; 480 } 481 } 482 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS; 483 } 484 } else { 485 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE; 486 } 487 488 mTbsGatt.setCallControlPointResult( 489 request.device, request.requestedOpcode, request.callIndex, result); 490 491 bearer.requestMap.remove(requestId); 492 } 493 getTbsResult(int result, int requestedOpcode)494 private synchronized int getTbsResult(int result, int requestedOpcode) { 495 if (result == BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID) { 496 return TbsGatt.CALL_CONTROL_POINT_RESULT_INVALID_CALL_INDEX; 497 } 498 499 if (result == BluetoothLeCallControl.RESULT_ERROR_INVALID_URI 500 && requestedOpcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_ORIGINATE) { 501 return TbsGatt.CALL_CONTROL_POINT_RESULT_INVALID_OUTGOING_URI; 502 } 503 504 return TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE; 505 } 506 requestResult(int ccid, int requestId, int result)507 public synchronized void requestResult(int ccid, int requestId, int result) { 508 Log.d(TAG, "requestResult: ccid=" + ccid + " requestId=" + requestId + " result=" + result); 509 510 if (!mIsInitialized) { 511 Log.w(TAG, "requestResult called while not initialized."); 512 return; 513 } 514 515 Bearer bearer = getBearerByCcid(ccid); 516 if (bearer == null) { 517 Log.i(TAG, " Bearer for ccid " + ccid + " does not exist"); 518 return; 519 } 520 521 if (result == BluetoothLeCallControl.RESULT_SUCCESS) { 522 // don't send the success here, wait for state transition instead 523 return; 524 } 525 526 // check if there's any pending request related to this call 527 Request request = bearer.requestMap.remove(requestId); 528 if (request == null) { 529 // already sent response 530 return; 531 } 532 533 int tbsResult = getTbsResult(result, request.requestedOpcode); 534 mTbsGatt.setCallControlPointResult( 535 request.device, request.requestedOpcode, request.callIndex, tbsResult); 536 } 537 callAdded(int ccid, BluetoothLeCall call)538 public synchronized void callAdded(int ccid, BluetoothLeCall call) { 539 Log.d(TAG, "callAdded: ccid=" + ccid + " call=" + call); 540 541 if (!mIsInitialized) { 542 Log.w(TAG, "callAdded called while not initialized."); 543 return; 544 } 545 546 Bearer bearer = getBearerByCcid(ccid); 547 if (bearer == null) { 548 Log.e(TAG, "callAdded: unknown ccid=" + ccid); 549 return; 550 } 551 552 UUID callId = call.getUuid(); 553 if (bearer.callIdIndexMap.containsKey(callId)) { 554 Log.e(TAG, "callAdded: uuidId=" + callId + " already on list"); 555 return; 556 } 557 558 Integer callIndex = getFreeCallIndex(); 559 if (callIndex == null) { 560 Log.e(TAG, "callAdded: out of call indices!"); 561 return; 562 } 563 564 bearer.callIdIndexMap.put(callId, callIndex); 565 TbsCall tbsCall = TbsCall.create(call); 566 mCurrentCallsList.put(callIndex, tbsCall); 567 568 checkRequestComplete(bearer, callId, tbsCall); 569 if (tbsCall.isIncoming()) { 570 mTbsGatt.setIncomingCall(callIndex, tbsCall.getUri()); 571 } 572 573 String friendlyName = tbsCall.getFriendlyName(); 574 if (friendlyName == null) { 575 friendlyName = UNKNOWN_FRIENDLY_NAME; 576 } 577 mTbsGatt.setCallFriendlyName(callIndex, friendlyName); 578 579 notifyCclc(); 580 if (mForegroundBearer != bearer) { 581 setForegroundBearer(bearer); 582 } 583 } 584 callRemoved(int ccid, UUID callId, int reason)585 public synchronized void callRemoved(int ccid, UUID callId, int reason) { 586 Log.d(TAG, "callRemoved: ccid=" + ccid + "reason=" + reason); 587 588 if (!mIsInitialized) { 589 Log.w(TAG, "callRemoved called while not initialized."); 590 return; 591 } 592 593 Bearer bearer = getBearerByCcid(ccid); 594 if (bearer == null) { 595 Log.e(TAG, "callRemoved: unknown ccid=" + ccid); 596 return; 597 } 598 599 Integer callIndex = bearer.callIdIndexMap.remove(callId); 600 if (callIndex == null) { 601 Log.e(TAG, "callIndex: is null for callId" + callId); 602 return; 603 } 604 605 TbsCall tbsCall = mCurrentCallsList.remove(callIndex); 606 if (tbsCall == null) { 607 Log.e(TAG, "callRemoved: no such call"); 608 return; 609 } 610 611 checkRequestComplete(bearer, callId, tbsCall); 612 mTbsGatt.setTerminationReason(callIndex, reason); 613 notifyCclc(); 614 615 Integer incomingCallIndex = mTbsGatt.getIncomingCallIndex(); 616 if (incomingCallIndex != null && incomingCallIndex.equals(callIndex)) { 617 mTbsGatt.clearIncomingCall(); 618 // TODO: check if there's any incoming call more??? 619 } 620 621 Integer friendlyNameCallIndex = mTbsGatt.getCallFriendlyNameIndex(); 622 if (friendlyNameCallIndex != null && friendlyNameCallIndex.equals(callIndex)) { 623 mTbsGatt.clearFriendlyName(); 624 // TODO: check if there's any incoming/outgoing call more??? 625 } 626 } 627 callStateChanged(int ccid, UUID callId, int state)628 public synchronized void callStateChanged(int ccid, UUID callId, int state) { 629 Log.d(TAG, "callStateChanged: ccid=" + ccid + " callId=" + callId + " state=" + state); 630 631 if (!mIsInitialized) { 632 Log.w(TAG, "callStateChanged called while not initialized."); 633 return; 634 } 635 636 Bearer bearer = getBearerByCcid(ccid); 637 if (bearer == null) { 638 Log.e(TAG, "callStateChanged: unknown ccid=" + ccid); 639 return; 640 } 641 642 Integer callIndex = bearer.callIdIndexMap.get(callId); 643 if (callIndex == null) { 644 Log.e(TAG, "callStateChanged: unknown callId=" + callId); 645 return; 646 } 647 648 TbsCall tbsCall = mCurrentCallsList.get(callIndex); 649 if (tbsCall.getState() == state) { 650 return; 651 } 652 653 tbsCall.setState(state); 654 655 checkRequestComplete(bearer, callId, tbsCall); 656 notifyCclc(); 657 658 Integer incomingCallIndex = mTbsGatt.getIncomingCallIndex(); 659 if (incomingCallIndex != null && incomingCallIndex.equals(callIndex)) { 660 mTbsGatt.clearIncomingCall(); 661 // TODO: check if there's any incoming call more??? 662 } 663 } 664 currentCallsList(int ccid, List<BluetoothLeCall> calls)665 public synchronized void currentCallsList(int ccid, List<BluetoothLeCall> calls) { 666 Log.d(TAG, "currentCallsList: ccid=" + ccid + " callsNum=" + calls.size()); 667 668 if (!mIsInitialized) { 669 Log.w(TAG, "currentCallsList called while not initialized."); 670 return; 671 } 672 673 Bearer bearer = getBearerByCcid(ccid); 674 if (bearer == null) { 675 Log.e(TAG, "currentCallsList: unknown ccid=" + ccid); 676 return; 677 } 678 679 boolean cclc = false; 680 Map<UUID, Integer> storedCallIdList = new HashMap<>(bearer.callIdIndexMap); 681 bearer.callIdIndexMap = new HashMap<>(); 682 for (BluetoothLeCall call : calls) { 683 UUID callId = call.getUuid(); 684 Integer callIndex = storedCallIdList.get(callId); 685 if (callIndex == null) { 686 // new call 687 callIndex = getFreeCallIndex(); 688 if (callIndex == null) { 689 Log.e(TAG, "currentCallsList: out of call indices!"); 690 continue; 691 } 692 693 mCurrentCallsList.put(callIndex, TbsCall.create(call)); 694 cclc |= true; 695 } else { 696 TbsCall tbsCall = mCurrentCallsList.get(callIndex); 697 TbsCall tbsCallNew = TbsCall.create(call); 698 if (tbsCall != tbsCallNew) { 699 mCurrentCallsList.replace(callIndex, tbsCallNew); 700 cclc |= true; 701 } 702 } 703 704 bearer.callIdIndexMap.put(callId, callIndex); 705 } 706 707 for (Map.Entry<UUID, Integer> callIdToIndex : storedCallIdList.entrySet()) { 708 if (!bearer.callIdIndexMap.containsKey(callIdToIndex.getKey())) { 709 mCurrentCallsList.remove(callIdToIndex.getValue()); 710 cclc |= true; 711 } 712 } 713 714 if (cclc) { 715 notifyCclc(); 716 } 717 } 718 networkStateChanged(int ccid, String providerName, int technology)719 public synchronized void networkStateChanged(int ccid, String providerName, int technology) { 720 Log.d( 721 TAG, 722 "networkStateChanged: ccid=" 723 + ccid 724 + " providerName=" 725 + providerName 726 + " technology=" 727 + technology); 728 729 if (!mIsInitialized) { 730 Log.w(TAG, "networkStateChanged called while not initialized."); 731 return; 732 } 733 734 Bearer bearer = getBearerByCcid(ccid); 735 if (bearer == null) { 736 return; 737 } 738 739 boolean providerChanged = !bearer.providerName.equals(providerName); 740 if (providerChanged) { 741 bearer.providerName = providerName; 742 } 743 744 boolean technologyChanged = bearer.technology != technology; 745 if (technologyChanged) { 746 bearer.technology = technology; 747 } 748 749 if (bearer == mForegroundBearer) { 750 if (providerChanged) { 751 mTbsGatt.setBearerProviderName(bearer.providerName); 752 } 753 754 if (technologyChanged) { 755 mTbsGatt.setBearerTechnology(bearer.technology); 756 } 757 } 758 } 759 processOriginateCall(BluetoothDevice device, String uri)760 private synchronized int processOriginateCall(BluetoothDevice device, String uri) { 761 if (uri.startsWith("tel")) { 762 /* 763 * FIXME: For now, process telephone call originate request here, as 764 * BluetoothInCallService might be not running. The BluetoothInCallService is active 765 * when there is a call only. 766 */ 767 Log.i(TAG, "originate uri=" + uri); 768 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, Uri.parse(uri)); 769 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 770 mTbsGatt.getContext().startActivity(intent); 771 mTbsGatt.setCallControlPointResult( 772 device, 773 TbsGatt.CALL_CONTROL_POINT_OPCODE_ORIGINATE, 774 TbsCall.INDEX_UNASSIGNED, 775 TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS); 776 } else { 777 UUID callId = UUID.randomUUID(); 778 int requestId = mLastRequestIdAssigned + 1; 779 Request request = 780 new Request( 781 device, 782 callId, 783 TbsGatt.CALL_CONTROL_POINT_OPCODE_ORIGINATE, 784 TbsCall.INDEX_UNASSIGNED); 785 786 Bearer bearer = getBearerSupportingUri(uri); 787 if (bearer == null) { 788 return TbsGatt.CALL_CONTROL_POINT_RESULT_INVALID_OUTGOING_URI; 789 } 790 791 try { 792 bearer.callback.onPlaceCall(requestId, new ParcelUuid(callId), uri); 793 } catch (RemoteException e) { 794 e.printStackTrace(); 795 return TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE; 796 } 797 798 bearer.requestMap.put(requestId, request); 799 mLastIndexAssigned = requestId; 800 } 801 802 setActiveLeDevice(device); 803 return TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS; 804 } 805 806 private final TbsGatt.Callback mTbsGattCallback = 807 new TbsGatt.Callback() { 808 809 @Override 810 public void onServiceAdded(boolean success) { 811 synchronized (TbsGeneric.this) { 812 Log.d(TAG, "onServiceAdded: success=" + success); 813 } 814 } 815 816 @Override 817 public boolean isInbandRingtoneEnabled(BluetoothDevice device) { 818 if (!isLeAudioServiceAvailable()) { 819 Log.i(TAG, "LeAudio service not available"); 820 return false; 821 } 822 int groupId = mLeAudioService.getGroupId(device); 823 return mLeAudioService.isInbandRingtoneEnabled(groupId); 824 } 825 826 @Override 827 public void onCallControlPointRequest( 828 BluetoothDevice device, int opcode, byte[] args) { 829 synchronized (TbsGeneric.this) { 830 Log.d( 831 TAG, 832 "onCallControlPointRequest: device=" 833 + device 834 + " opcode=" 835 + callControlRequestOpcodeStr(opcode) 836 + "(" 837 + opcode 838 + ")" 839 + " argsLen=" 840 + args.length); 841 842 if (!mIsInitialized) { 843 Log.w(TAG, "onCallControlPointRequest called while not initialized."); 844 return; 845 } 846 847 int result; 848 849 switch (opcode) { 850 case TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT: 851 case TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE: 852 case TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD: 853 case TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE: 854 { 855 if (args.length == 0) { 856 result = 857 TbsGatt 858 .CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE; 859 break; 860 } 861 862 int callIndex = args[0]; 863 Map.Entry<UUID, Bearer> entry = getCallIdByIndex(callIndex); 864 if (entry == null) { 865 result = 866 TbsGatt 867 .CALL_CONTROL_POINT_RESULT_INVALID_CALL_INDEX; 868 break; 869 } 870 871 TbsCall call = mCurrentCallsList.get(callIndex); 872 if (!isCallStateTransitionValid(call.getState(), opcode)) { 873 result = TbsGatt.CALL_CONTROL_POINT_RESULT_STATE_MISMATCH; 874 break; 875 } 876 877 Bearer bearer = entry.getValue(); 878 UUID callId = entry.getKey(); 879 int requestId = mLastRequestIdAssigned + 1; 880 Request request = 881 new Request(device, callId, opcode, callIndex); 882 try { 883 if (opcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT) { 884 setActiveLeDevice(device); 885 bearer.callback.onAcceptCall( 886 requestId, new ParcelUuid(callId)); 887 } else if (opcode 888 == TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE) { 889 bearer.callback.onTerminateCall( 890 requestId, new ParcelUuid(callId)); 891 } else if (opcode 892 == TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD) { 893 if ((bearer.capabilities 894 & BluetoothLeCallControl 895 .CAPABILITY_HOLD_CALL) 896 == 0) { 897 result = 898 TbsGatt 899 .CALL_CONTROL_POINT_RESULT_OPCODE_NOT_SUPPORTED; 900 break; 901 } 902 bearer.callback.onHoldCall( 903 requestId, new ParcelUuid(callId)); 904 } else { 905 if ((bearer.capabilities 906 & BluetoothLeCallControl 907 .CAPABILITY_HOLD_CALL) 908 == 0) { 909 result = 910 TbsGatt 911 .CALL_CONTROL_POINT_RESULT_OPCODE_NOT_SUPPORTED; 912 break; 913 } 914 bearer.callback.onUnholdCall( 915 requestId, new ParcelUuid(callId)); 916 } 917 } catch (RemoteException e) { 918 e.printStackTrace(); 919 result = 920 TbsGatt 921 .CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE; 922 break; 923 } 924 925 bearer.requestMap.put(requestId, request); 926 mLastRequestIdAssigned = requestId; 927 928 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS; 929 break; 930 } 931 932 case TbsGatt.CALL_CONTROL_POINT_OPCODE_ORIGINATE: 933 { 934 result = processOriginateCall(device, new String(args)); 935 break; 936 } 937 938 case TbsGatt.CALL_CONTROL_POINT_OPCODE_JOIN: 939 { 940 // at least 2 call indices are required 941 if (args.length < 2) { 942 result = 943 TbsGatt 944 .CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE; 945 break; 946 } 947 948 Map.Entry<UUID, Bearer> firstEntry = null; 949 List<ParcelUuid> parcelUuids = new ArrayList<>(); 950 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS; 951 for (int callIndex : args) { 952 Map.Entry<UUID, Bearer> entry = getCallIdByIndex(callIndex); 953 if (entry == null) { 954 result = 955 TbsGatt 956 .CALL_CONTROL_POINT_RESULT_INVALID_CALL_INDEX; 957 break; 958 } 959 960 // state transition is valid, because a call in any state 961 // can requested to join 962 963 if (firstEntry == null) { 964 firstEntry = entry; 965 } 966 967 if (firstEntry.getValue() != entry.getValue()) { 968 Log.w(TAG, "Cannot join calls from different bearers!"); 969 result = 970 TbsGatt 971 .CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE; 972 break; 973 } 974 975 parcelUuids.add(new ParcelUuid(entry.getKey())); 976 } 977 978 if (result != TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS) { 979 break; 980 } 981 982 Bearer bearer = firstEntry.getValue(); 983 Request request = 984 new Request(device, parcelUuids, opcode, args[0]); 985 int requestId = mLastRequestIdAssigned + 1; 986 try { 987 bearer.callback.onJoinCalls(requestId, parcelUuids); 988 } catch (RemoteException e) { 989 e.printStackTrace(); 990 result = 991 TbsGatt 992 .CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE; 993 break; 994 } 995 996 bearer.requestMap.put(requestId, request); 997 mLastIndexAssigned = requestId; 998 999 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS; 1000 break; 1001 } 1002 1003 default: 1004 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPCODE_NOT_SUPPORTED; 1005 break; 1006 } 1007 1008 if (result == TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS) { 1009 // return here and wait for the request completition from application 1010 return; 1011 } 1012 1013 mTbsGatt.setCallControlPointResult(device, opcode, 0, result); 1014 } 1015 } 1016 }; 1017 callControlRequestOpcodeStr(int opcode)1018 private String callControlRequestOpcodeStr(int opcode) { 1019 switch (opcode) { 1020 case TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT: 1021 return "ACCEPT"; 1022 case TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE: 1023 return "TERMINATE"; 1024 case TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD: 1025 return "LOCAL_HOLD"; 1026 case TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE: 1027 return "LOCAL_RETRIEVE"; 1028 default: 1029 return "UNKNOWN"; 1030 } 1031 } 1032 isCcidValid(int ccid)1033 private static boolean isCcidValid(int ccid) { 1034 return ccid != ContentControlIdKeeper.CCID_INVALID; 1035 } 1036 isCallIndexAssigned(int callIndex)1037 private static boolean isCallIndexAssigned(int callIndex) { 1038 return callIndex != TbsCall.INDEX_UNASSIGNED; 1039 } 1040 getFreeCallIndex()1041 private synchronized Integer getFreeCallIndex() { 1042 int callIndex = mLastIndexAssigned; 1043 for (int i = TbsCall.INDEX_MIN; i <= TbsCall.INDEX_MAX; i++) { 1044 callIndex = (callIndex + 1) % TbsCall.INDEX_MAX; 1045 if (!isCallIndexAssigned(callIndex)) { 1046 continue; 1047 } 1048 1049 if (mCurrentCallsList.keySet().contains(callIndex)) { 1050 continue; 1051 } 1052 1053 mLastIndexAssigned = callIndex; 1054 1055 return callIndex; 1056 } 1057 1058 return null; 1059 } 1060 getCallByStates( LinkedHashSet<Integer> states)1061 private synchronized Map.Entry<Integer, TbsCall> getCallByStates( 1062 LinkedHashSet<Integer> states) { 1063 for (Map.Entry<Integer, TbsCall> entry : mCurrentCallsList.entrySet()) { 1064 if (states.contains(entry.getValue().getState())) { 1065 return entry; 1066 } 1067 } 1068 1069 return null; 1070 } 1071 getForegroundCall()1072 private synchronized Map.Entry<Integer, TbsCall> getForegroundCall() { 1073 LinkedHashSet<Integer> states = new LinkedHashSet<Integer>(); 1074 Map.Entry<Integer, TbsCall> foregroundCall; 1075 1076 if (mCurrentCallsList.size() == 0) { 1077 return null; 1078 } 1079 1080 states.add(BluetoothLeCall.STATE_INCOMING); 1081 foregroundCall = getCallByStates(states); 1082 if (foregroundCall != null) { 1083 return foregroundCall; 1084 } 1085 1086 states.clear(); 1087 states.add(BluetoothLeCall.STATE_DIALING); 1088 states.add(BluetoothLeCall.STATE_ALERTING); 1089 foregroundCall = getCallByStates(states); 1090 if (foregroundCall != null) { 1091 return foregroundCall; 1092 } 1093 1094 states.clear(); 1095 states.add(BluetoothLeCall.STATE_ACTIVE); 1096 foregroundCall = getCallByStates(states); 1097 if (foregroundCall != null) { 1098 return foregroundCall; 1099 } 1100 1101 return null; 1102 } 1103 findNewForegroundBearer()1104 private synchronized Bearer findNewForegroundBearer() { 1105 if (mBearerList.size() == 0) { 1106 return null; 1107 } 1108 1109 // the bearer that owns the foreground call 1110 Map.Entry<Integer, TbsCall> foregroundCall = getForegroundCall(); 1111 if (foregroundCall != null) { 1112 for (Bearer bearer : mBearerList) { 1113 if (bearer.callIdIndexMap.values().contains(foregroundCall.getKey())) { 1114 return bearer; 1115 } 1116 } 1117 } 1118 1119 // the last bearer registered 1120 return mBearerList.get(mBearerList.size() - 1); 1121 } 1122 setForegroundBearer(Bearer bearer)1123 private synchronized void setForegroundBearer(Bearer bearer) { 1124 Log.d(TAG, "setForegroundBearer: bearer=" + bearer); 1125 1126 if (bearer == null) { 1127 mTbsGatt.setBearerProviderName(DEFAULT_PROVIDER_NAME); 1128 mTbsGatt.setBearerTechnology(DEFAULT_BEARER_TECHNOLOGY); 1129 } else if (mForegroundBearer == null) { 1130 mTbsGatt.setBearerProviderName(bearer.providerName); 1131 mTbsGatt.setBearerTechnology(bearer.technology); 1132 } else { 1133 if (!bearer.providerName.equals(mForegroundBearer.providerName)) { 1134 mTbsGatt.setBearerProviderName(bearer.providerName); 1135 } 1136 1137 if (bearer.technology != mForegroundBearer.technology) { 1138 mTbsGatt.setBearerTechnology(bearer.technology); 1139 } 1140 } 1141 1142 mForegroundBearer = bearer; 1143 } 1144 isLeAudioServiceAvailable()1145 private boolean isLeAudioServiceAvailable() { 1146 if (mLeAudioService != null) { 1147 return true; 1148 } 1149 1150 mLeAudioService = mFactory.getLeAudioService(); 1151 if (mLeAudioService == null) { 1152 Log.e(TAG, "leAudioService not available"); 1153 return false; 1154 } 1155 1156 return true; 1157 } 1158 1159 @VisibleForTesting setLeAudioServiceForTesting(LeAudioService leAudioService)1160 void setLeAudioServiceForTesting(LeAudioService leAudioService) { 1161 mLeAudioService = leAudioService; 1162 } 1163 notifyCclc()1164 private synchronized void notifyCclc() { 1165 Log.d(TAG, "notifyCclc"); 1166 1167 if (isLeAudioServiceAvailable()) { 1168 if (mCurrentCallsList.size() > 0) { 1169 mLeAudioService.setInCall(true); 1170 } else { 1171 mLeAudioService.setInCall(false); 1172 } 1173 } 1174 1175 mTbsGatt.setCallState(mCurrentCallsList); 1176 mTbsGatt.setBearerListCurrentCalls(mCurrentCallsList); 1177 } 1178 updateUriSchemesSupported()1179 private synchronized void updateUriSchemesSupported() { 1180 List<String> newUriSchemes = new ArrayList<>(); 1181 for (Bearer bearer : mBearerList) { 1182 newUriSchemes.addAll(bearer.uriSchemes); 1183 } 1184 1185 // filter duplicates 1186 newUriSchemes = new ArrayList<>(new HashSet<>(newUriSchemes)); 1187 if (newUriSchemes.equals(mUriSchemes)) { 1188 return; 1189 } 1190 1191 mUriSchemes = new ArrayList<>(newUriSchemes); 1192 mTbsGatt.setBearerUriSchemesSupportedList(mUriSchemes); 1193 } 1194 setActiveLeDevice(BluetoothDevice device)1195 private void setActiveLeDevice(BluetoothDevice device) { 1196 if (device == null) { 1197 Log.w(TAG, "setActiveLeDevice: ignore null device"); 1198 return; 1199 } 1200 if (!isLeAudioServiceAvailable()) { 1201 Log.w(TAG, "mLeAudioService not available"); 1202 return; 1203 } 1204 mLeAudioService.setActiveDevice(device); 1205 } 1206 isCallStateTransitionValid(int callState, int requestedOpcode)1207 private static boolean isCallStateTransitionValid(int callState, int requestedOpcode) { 1208 switch (requestedOpcode) { 1209 case TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT: 1210 if (callState == BluetoothLeCall.STATE_INCOMING) { 1211 return true; 1212 } 1213 break; 1214 1215 case TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE: 1216 // Any call can be terminated. 1217 return true; 1218 1219 case TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD: 1220 if (callState == BluetoothLeCall.STATE_INCOMING 1221 || callState == BluetoothLeCall.STATE_ACTIVE 1222 || callState == BluetoothLeCall.STATE_REMOTELY_HELD) { 1223 return true; 1224 } 1225 break; 1226 1227 case TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE: 1228 if (callState == BluetoothLeCall.STATE_LOCALLY_HELD 1229 || callState == BluetoothLeCall.STATE_LOCALLY_AND_REMOTELY_HELD) { 1230 return true; 1231 } 1232 break; 1233 1234 default: 1235 Log.e(TAG, "unhandled opcode " + requestedOpcode); 1236 } 1237 1238 return false; 1239 } 1240 1241 /** 1242 * Dump status of TBS service along with related objects 1243 * 1244 * @param sb string builder object that TBS module will be appending 1245 */ dump(StringBuilder sb)1246 public void dump(StringBuilder sb) { 1247 sb.append("\tRinger Mode: " + mStoredRingerMode); 1248 1249 sb.append("\n\tCurrent call list:"); 1250 for (TbsCall call : mCurrentCallsList.values()) { 1251 sb.append("\n\t\tFriendly name: " + call.getSafeFriendlyName()); 1252 sb.append("\n\t\t\tState: " + TbsCall.stateToString(call.getState())); 1253 sb.append("\n\t\t\tURI: " + call.getSafeUri()); 1254 sb.append("\n\t\t\tFlags: " + TbsCall.flagsToString(call.getFlags())); 1255 } 1256 1257 mTbsGatt.dump(sb); 1258 } 1259 } 1260