1 /* 2 * Copyright 2019 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 android.bluetooth; 19 20 import static android.Manifest.permission.BLUETOOTH_CONNECT; 21 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; 22 import static android.bluetooth.BluetoothUtils.executeFromBinder; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.RequiresNoPermission; 27 import android.annotation.RequiresPermission; 28 import android.annotation.SuppressLint; 29 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 30 import android.content.AttributionSource; 31 import android.content.Context; 32 import android.os.IBinder; 33 import android.os.ParcelUuid; 34 import android.os.RemoteException; 35 import android.util.Log; 36 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.UUID; 40 import java.util.concurrent.Executor; 41 42 /** 43 * This class provides the APIs to control the Call Control profile. 44 * 45 * <p>This class provides Bluetooth Telephone Bearer Service functionality, allowing applications to 46 * expose a GATT Service based interface to control the state of the calls by remote devices such as 47 * LE audio devices. 48 * 49 * <p>BluetoothLeCallControl is a proxy object for controlling the Bluetooth Telephone Bearer 50 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothLeCallControl 51 * proxy object. 52 * 53 * @hide 54 */ 55 public final class BluetoothLeCallControl implements BluetoothProfile { 56 private static final String TAG = BluetoothLeCallControl.class.getSimpleName(); 57 58 /** 59 * The template class is used to call callback functions on events from the TBS server. Callback 60 * functions are wrapped in this class and registered to the Android system during app 61 * registration. 62 * 63 * @hide 64 */ 65 public abstract static class Callback { 66 private static final String TAG = 67 BluetoothLeCallControl.TAG + "." + Callback.class.getSimpleName(); 68 69 /** 70 * Called when a remote client requested to accept the call. 71 * 72 * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the 73 * request. 74 * 75 * @param requestId The Id of the request 76 * @param callId The call Id requested to be accepted 77 * @hide 78 */ onAcceptCall(int requestId, @NonNull UUID callId)79 public abstract void onAcceptCall(int requestId, @NonNull UUID callId); 80 81 /** 82 * A remote client has requested to terminate the call. 83 * 84 * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the 85 * request. 86 * 87 * @param requestId The Id of the request 88 * @param callId The call Id requested to terminate 89 * @hide 90 */ onTerminateCall(int requestId, @NonNull UUID callId)91 public abstract void onTerminateCall(int requestId, @NonNull UUID callId); 92 93 /** 94 * A remote client has requested to hold the call. 95 * 96 * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the 97 * request. 98 * 99 * @param requestId The Id of the request 100 * @param callId The call Id requested to be put on hold 101 * @hide 102 */ onHoldCall(int requestId, @NonNull UUID callId)103 public void onHoldCall(int requestId, @NonNull UUID callId) { 104 Log.e(TAG, "onHoldCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); 105 } 106 107 /** 108 * A remote client has requested to unhold the call. 109 * 110 * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the 111 * request. 112 * 113 * @param requestId The Id of the request 114 * @param callId The call Id requested to unhold 115 * @hide 116 */ onUnholdCall(int requestId, @NonNull UUID callId)117 public void onUnholdCall(int requestId, @NonNull UUID callId) { 118 Log.e(TAG, "onUnholdCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); 119 } 120 121 /** 122 * A remote client has requested to place a call. 123 * 124 * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the 125 * request. 126 * 127 * @param requestId The Id of the request 128 * @param callId The Id to be assigned for the new call 129 * @param uri The caller URI requested 130 * @hide 131 */ onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri)132 public abstract void onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri); 133 134 /** 135 * A remote client has requested to join the calls. 136 * 137 * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the 138 * request. 139 * 140 * @param requestId The Id of the request 141 * @param callIds The call Id list requested to join 142 * @hide 143 */ onJoinCalls(int requestId, @NonNull List<UUID> callIds)144 public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) { 145 Log.e(TAG, "onJoinCalls: unimplemented, however CAPABILITY_JOIN_CALLS is set!"); 146 } 147 } 148 149 private class CallbackWrapper extends IBluetoothLeCallControlCallback.Stub { 150 151 private final Executor mExecutor; 152 private final Callback mCallback; 153 CallbackWrapper(Executor executor, Callback callback)154 CallbackWrapper(Executor executor, Callback callback) { 155 mExecutor = executor; 156 mCallback = callback; 157 } 158 159 @Override onBearerRegistered(int ccid)160 public void onBearerRegistered(int ccid) { 161 if (mCallback != null) { 162 Log.d(TAG, "onBearerRegistered: ccid is " + ccid); 163 mCcid = ccid; 164 } else { 165 // registration timeout 166 Log.e(TAG, "onBearerRegistered: mCallback is null"); 167 } 168 } 169 170 @Override onAcceptCall(int requestId, ParcelUuid uuid)171 public void onAcceptCall(int requestId, ParcelUuid uuid) { 172 executeFromBinder(mExecutor, () -> mCallback.onAcceptCall(requestId, uuid.getUuid())); 173 } 174 175 @Override onTerminateCall(int requestId, ParcelUuid uuid)176 public void onTerminateCall(int requestId, ParcelUuid uuid) { 177 executeFromBinder( 178 mExecutor, () -> mCallback.onTerminateCall(requestId, uuid.getUuid())); 179 } 180 181 @Override onHoldCall(int requestId, ParcelUuid uuid)182 public void onHoldCall(int requestId, ParcelUuid uuid) { 183 executeFromBinder(mExecutor, () -> mCallback.onHoldCall(requestId, uuid.getUuid())); 184 } 185 186 @Override onUnholdCall(int requestId, ParcelUuid uuid)187 public void onUnholdCall(int requestId, ParcelUuid uuid) { 188 executeFromBinder(mExecutor, () -> mCallback.onUnholdCall(requestId, uuid.getUuid())); 189 } 190 191 @Override onPlaceCall(int requestId, ParcelUuid uuid, String uri)192 public void onPlaceCall(int requestId, ParcelUuid uuid, String uri) { 193 executeFromBinder( 194 mExecutor, () -> mCallback.onPlaceCall(requestId, uuid.getUuid(), uri)); 195 } 196 197 @Override onJoinCalls(int requestId, List<ParcelUuid> parcelUuids)198 public void onJoinCalls(int requestId, List<ParcelUuid> parcelUuids) { 199 List<UUID> uuids = new ArrayList<>(); 200 for (ParcelUuid parcelUuid : parcelUuids) { 201 uuids.add(parcelUuid.getUuid()); 202 } 203 204 executeFromBinder(mExecutor, () -> mCallback.onJoinCalls(requestId, uuids)); 205 } 206 } 207 ; 208 209 private final BluetoothAdapter mAdapter; 210 private final AttributionSource mAttributionSource; 211 private int mCcid = 0; 212 private String mToken; 213 private Callback mCallback = null; 214 215 private IBluetoothLeCallControl mService; 216 217 /** 218 * Create a BluetoothLeCallControl proxy object for interacting with the local Bluetooth 219 * telephone bearer service. 220 */ BluetoothLeCallControl(Context context, BluetoothAdapter adapter)221 /* package */ BluetoothLeCallControl(Context context, BluetoothAdapter adapter) { 222 mAdapter = adapter; 223 mAttributionSource = mAdapter.getAttributionSource(); 224 mService = null; 225 } 226 227 /** @hide */ close()228 public void close() { 229 Log.v(TAG, "close()"); 230 231 mAdapter.closeProfileProxy(this); 232 } 233 234 /** @hide */ 235 @Override 236 @RequiresNoPermission onServiceConnected(IBinder service)237 public void onServiceConnected(IBinder service) { 238 mService = IBluetoothLeCallControl.Stub.asInterface(service); 239 } 240 241 /** @hide */ 242 @Override 243 @RequiresNoPermission onServiceDisconnected()244 public void onServiceDisconnected() { 245 mService = null; 246 } 247 getService()248 private IBluetoothLeCallControl getService() { 249 return mService; 250 } 251 252 /** @hide */ 253 @Override 254 @RequiresNoPermission getAdapter()255 public BluetoothAdapter getAdapter() { 256 return mAdapter; 257 } 258 259 /** 260 * Not supported 261 * 262 * @throws UnsupportedOperationException on every call 263 */ 264 @Override 265 @RequiresNoPermission getConnectionState(@ullable BluetoothDevice device)266 public int getConnectionState(@Nullable BluetoothDevice device) { 267 throw new UnsupportedOperationException("not supported"); 268 } 269 270 /** 271 * Not supported 272 * 273 * @throws UnsupportedOperationException on every call 274 */ 275 @Override 276 @RequiresNoPermission getConnectedDevices()277 public @NonNull List<BluetoothDevice> getConnectedDevices() { 278 throw new UnsupportedOperationException("not supported"); 279 } 280 281 /** 282 * Not supported 283 * 284 * @throws UnsupportedOperationException on every call 285 */ 286 @Override 287 @RequiresNoPermission 288 @NonNull getDevicesMatchingConnectionStates(@onNull int[] states)289 public List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) { 290 throw new UnsupportedOperationException("not supported"); 291 } 292 293 /** 294 * Register Telephone Bearer exposing the interface that allows remote devices to track and 295 * control the call states. 296 * 297 * <p>This is an asynchronous call. The callback is used to notify success or failure if the 298 * function returns true. 299 * 300 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 301 * <!-- The UCI is a String identifier of the telephone bearer as defined at 302 * https://www.bluetooth.com/specifications/assigned-numbers/uniform-caller-identifiers 303 * (login required). --> 304 * <!-- The examples of common URI schemes can be found in 305 * https://iana.org/assignments/uri-schemes/uri-schemes.xhtml --> 306 * <!-- The Technology is an integer value. The possible values are defined at 307 * https://www.bluetooth.com/specifications/assigned-numbers (login required). 308 * --> 309 * 310 * @param uci Bearer Unique Client Identifier 311 * @param uriSchemes URI Schemes supported list 312 * @param capabilities bearer capabilities 313 * @param provider Network provider name 314 * @param technology Network technology 315 * @param executor {@link Executor} object on which callback will be executed. The Executor 316 * object is required. 317 * @param callback {@link Callback} object to which callback messages will be sent. The Callback 318 * object is required. 319 * @return true on success, false otherwise 320 * @hide 321 */ 322 @SuppressLint("ExecutorRegistration") 323 @RequiresBluetoothConnectPermission 324 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) registerBearer( @ullable String uci, @NonNull List<String> uriSchemes, int capabilities, @NonNull String provider, int technology, @NonNull Executor executor, @NonNull Callback callback)325 public boolean registerBearer( 326 @Nullable String uci, 327 @NonNull List<String> uriSchemes, 328 int capabilities, 329 @NonNull String provider, 330 int technology, 331 @NonNull Executor executor, 332 @NonNull Callback callback) { 333 Log.d(TAG, "registerBearer"); 334 if (callback == null) { 335 throw new IllegalArgumentException("null parameter: " + callback); 336 } 337 if (mCcid != 0) { 338 Log.e(TAG, "Ccid is already set to " + mCcid); 339 return false; 340 } 341 342 mToken = uci; 343 344 final IBluetoothLeCallControl service = getService(); 345 if (service == null) { 346 Log.w(TAG, "Proxy not attached to service"); 347 return false; 348 } 349 350 if (mCallback != null) { 351 Log.e(TAG, "Bearer can be opened only once"); 352 return false; 353 } 354 355 mCallback = callback; 356 try { 357 CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback); 358 service.registerBearer( 359 mToken, 360 callbackWrapper, 361 uci, 362 uriSchemes, 363 capabilities, 364 provider, 365 technology, 366 mAttributionSource); 367 368 } catch (RemoteException e) { 369 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 370 mCallback = null; 371 return false; 372 } 373 374 if (mCcid == 0) { 375 mCallback = null; 376 return false; 377 } 378 379 return true; 380 } 381 382 /** 383 * Unregister Telephone Bearer Service and destroy all the associated data. 384 * 385 * @hide 386 */ 387 @RequiresBluetoothConnectPermission 388 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) unregisterBearer()389 public void unregisterBearer() { 390 Log.d(TAG, "unregisterBearer"); 391 if (mCcid == 0) { 392 return; 393 } 394 395 final IBluetoothLeCallControl service = getService(); 396 if (service == null) { 397 Log.w(TAG, "Proxy not attached to service"); 398 return; 399 } 400 401 mCcid = 0; 402 mCallback = null; 403 404 try { 405 service.unregisterBearer(mToken, mAttributionSource); 406 } catch (RemoteException e) { 407 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 408 } 409 } 410 411 /** 412 * Notify about the newly added call. 413 * 414 * <p>This shall be called as early as possible after the call has been added. 415 * 416 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 417 * 418 * @param call Newly added call 419 * @hide 420 */ 421 @RequiresBluetoothConnectPermission 422 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) onCallAdded(@onNull BluetoothLeCall call)423 public void onCallAdded(@NonNull BluetoothLeCall call) { 424 Log.d(TAG, "onCallAdded: call=" + call); 425 if (mCcid == 0) { 426 return; 427 } 428 429 final IBluetoothLeCallControl service = getService(); 430 if (service == null) { 431 Log.w(TAG, "Proxy not attached to service"); 432 return; 433 } 434 435 try { 436 service.callAdded(mCcid, call, mAttributionSource); 437 } catch (RemoteException e) { 438 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 439 } 440 } 441 442 /** 443 * Notify about the removed call. 444 * 445 * <p>This shall be called as early as possible after the call has been removed. 446 * 447 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 448 * 449 * @param callId The Id of a call that has been removed 450 * @param reason Call termination reason 451 * @hide 452 */ 453 @RequiresBluetoothConnectPermission 454 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) onCallRemoved(@onNull UUID callId, int reason)455 public void onCallRemoved(@NonNull UUID callId, int reason) { 456 Log.d(TAG, "callRemoved: callId=" + callId); 457 if (mCcid == 0) { 458 return; 459 } 460 461 final IBluetoothLeCallControl service = getService(); 462 if (service == null) { 463 Log.w(TAG, "Proxy not attached to service"); 464 return; 465 } 466 try { 467 service.callRemoved(mCcid, new ParcelUuid(callId), reason, mAttributionSource); 468 } catch (RemoteException e) { 469 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 470 } 471 } 472 473 /** 474 * Notify the call state change 475 * 476 * <p>This shall be called as early as possible after the state of the call has changed. 477 * 478 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 479 * 480 * @param callId The call Id that state has been changed 481 * @param state Call state 482 * @hide 483 */ 484 @RequiresBluetoothConnectPermission 485 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) onCallStateChanged(@onNull UUID callId, @BluetoothLeCall.State int state)486 public void onCallStateChanged(@NonNull UUID callId, @BluetoothLeCall.State int state) { 487 Log.d(TAG, "callStateChanged: callId=" + callId + " state=" + state); 488 if (mCcid == 0) { 489 return; 490 } 491 492 final IBluetoothLeCallControl service = getService(); 493 if (service == null) { 494 Log.w(TAG, "Proxy not attached to service"); 495 return; 496 } 497 498 try { 499 service.callStateChanged(mCcid, new ParcelUuid(callId), state, mAttributionSource); 500 } catch (RemoteException e) { 501 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 502 } 503 } 504 505 /** 506 * Provide the current calls list 507 * 508 * <p>This function must be invoked after registration if application has any calls. 509 * 510 * @param calls current calls list 511 * @hide 512 */ 513 @RequiresBluetoothConnectPermission 514 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) currentCallsList(@onNull List<BluetoothLeCall> calls)515 public void currentCallsList(@NonNull List<BluetoothLeCall> calls) { 516 final IBluetoothLeCallControl service = getService(); 517 if (service == null) { 518 Log.w(TAG, "Proxy not attached to service"); 519 return; 520 } 521 522 try { 523 service.currentCallsList(mCcid, calls, mAttributionSource); 524 } catch (RemoteException e) { 525 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 526 } 527 } 528 529 /** 530 * Send a response to a call control request to a remote device. 531 * 532 * <p>This function must be invoked in when a request is received by one of these callback 533 * methods: 534 * 535 * <ul> 536 * <li>{@link Callback#onAcceptCall} 537 * <li>{@link Callback#onTerminateCall} 538 * <li>{@link Callback#onHoldCall} 539 * <li>{@link Callback#onUnholdCall} 540 * <li>{@link Callback#onPlaceCall} 541 * <li>{@link Callback#onJoinCalls} 542 * </ul> 543 * 544 * @param requestId The ID of the request that was received with the callback 545 * @param result The result of the request to be sent to the remote devices 546 */ 547 @RequiresBluetoothConnectPermission 548 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) requestResult(int requestId, int result)549 public void requestResult(int requestId, int result) { 550 Log.d(TAG, "requestResult: requestId=" + requestId + " result=" + result); 551 if (mCcid == 0) { 552 return; 553 } 554 555 final IBluetoothLeCallControl service = getService(); 556 if (service == null) { 557 Log.w(TAG, "Proxy not attached to service"); 558 return; 559 } 560 561 try { 562 service.requestResult(mCcid, requestId, result, mAttributionSource); 563 } catch (RemoteException e) { 564 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 565 } 566 } 567 } 568