1 /* 2 * Copyright 2018 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 android.bluetooth; 18 19 import android.Manifest; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SdkConstant; 24 import android.annotation.SuppressLint; 25 import android.annotation.SdkConstant.SdkConstantType; 26 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 27 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; 28 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; 29 import android.annotation.SystemApi; 30 import android.compat.annotation.UnsupportedAppUsage; 31 import android.content.Attributable; 32 import android.content.AttributionSource; 33 import android.content.Context; 34 import android.os.Binder; 35 import android.os.Build; 36 import android.os.IBinder; 37 import android.os.RemoteException; 38 import android.util.Log; 39 40 import java.util.ArrayList; 41 import java.util.List; 42 43 /** 44 * This class provides the public APIs to control the Hearing Aid profile. 45 * 46 * <p>BluetoothHearingAid is a proxy object for controlling the Bluetooth Hearing Aid 47 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get 48 * the BluetoothHearingAid proxy object. 49 * 50 * <p> Android only supports one set of connected Bluetooth Hearing Aid device at a time. Each 51 * method is protected with its appropriate permission. 52 */ 53 public final class BluetoothHearingAid implements BluetoothProfile { 54 private static final String TAG = "BluetoothHearingAid"; 55 private static final boolean DBG = true; 56 private static final boolean VDBG = false; 57 58 /** 59 * Intent used to broadcast the change in connection state of the Hearing Aid 60 * profile. Please note that in the binaural case, there will be two different LE devices for 61 * the left and right side and each device will have their own connection state changes.S 62 * 63 * <p>This intent will have 3 extras: 64 * <ul> 65 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 66 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> 67 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 68 * </ul> 69 * 70 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 71 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, 72 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. 73 */ 74 @RequiresLegacyBluetoothPermission 75 @RequiresBluetoothConnectPermission 76 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 77 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 78 public static final String ACTION_CONNECTION_STATE_CHANGED = 79 "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED"; 80 81 /** 82 * Intent used to broadcast the selection of a connected device as active. 83 * 84 * <p>This intent will have one extra: 85 * <ul> 86 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can 87 * be null if no device is active. </li> 88 * </ul> 89 * 90 * @hide 91 */ 92 @RequiresLegacyBluetoothPermission 93 @RequiresBluetoothConnectPermission 94 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 95 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 96 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 97 public static final String ACTION_ACTIVE_DEVICE_CHANGED = 98 "android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED"; 99 100 /** 101 * This device represents Left Hearing Aid. 102 * 103 * @hide 104 */ 105 public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT; 106 107 /** 108 * This device represents Right Hearing Aid. 109 * 110 * @hide 111 */ 112 public static final int SIDE_RIGHT = IBluetoothHearingAid.SIDE_RIGHT; 113 114 /** 115 * This device is Monaural. 116 * 117 * @hide 118 */ 119 public static final int MODE_MONAURAL = IBluetoothHearingAid.MODE_MONAURAL; 120 121 /** 122 * This device is Binaural (should receive only left or right audio). 123 * 124 * @hide 125 */ 126 public static final int MODE_BINAURAL = IBluetoothHearingAid.MODE_BINAURAL; 127 128 /** 129 * Indicates the HiSyncID could not be read and is unavailable. 130 * 131 * @hide 132 */ 133 public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID; 134 135 private final BluetoothAdapter mAdapter; 136 private final AttributionSource mAttributionSource; 137 private final BluetoothProfileConnector<IBluetoothHearingAid> mProfileConnector = 138 new BluetoothProfileConnector(this, BluetoothProfile.HEARING_AID, 139 "BluetoothHearingAid", IBluetoothHearingAid.class.getName()) { 140 @Override 141 public IBluetoothHearingAid getServiceInterface(IBinder service) { 142 return IBluetoothHearingAid.Stub.asInterface(Binder.allowBlocking(service)); 143 } 144 }; 145 146 /** 147 * Create a BluetoothHearingAid proxy object for interacting with the local 148 * Bluetooth Hearing Aid service. 149 */ BluetoothHearingAid(Context context, ServiceListener listener, BluetoothAdapter adapter)150 /* package */ BluetoothHearingAid(Context context, ServiceListener listener, 151 BluetoothAdapter adapter) { 152 mAdapter = adapter; 153 mAttributionSource = adapter.getAttributionSource(); 154 mProfileConnector.connect(context, listener); 155 } 156 close()157 /*package*/ void close() { 158 mProfileConnector.disconnect(); 159 } 160 getService()161 private IBluetoothHearingAid getService() { 162 return mProfileConnector.getService(); 163 } 164 165 /** 166 * Initiate connection to a profile of the remote bluetooth device. 167 * 168 * <p> This API returns false in scenarios like the profile on the 169 * device is already connected or Bluetooth is not turned on. 170 * When this API returns true, it is guaranteed that 171 * connection state intent for the profile will be broadcasted with 172 * the state. Users can get the connection state of the profile 173 * from this intent. 174 * 175 * @param device Remote Bluetooth Device 176 * @return false on immediate error, true otherwise 177 * @hide 178 */ 179 @RequiresBluetoothConnectPermission 180 @RequiresPermission(allOf = { 181 android.Manifest.permission.BLUETOOTH_CONNECT, 182 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 183 }) connect(BluetoothDevice device)184 public boolean connect(BluetoothDevice device) { 185 if (DBG) log("connect(" + device + ")"); 186 final IBluetoothHearingAid service = getService(); 187 try { 188 if (service != null && isEnabled() && isValidDevice(device)) { 189 return service.connect(device, mAttributionSource); 190 } 191 if (service == null) Log.w(TAG, "Proxy not attached to service"); 192 return false; 193 } catch (RemoteException e) { 194 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 195 return false; 196 } 197 } 198 199 /** 200 * Initiate disconnection from a profile 201 * 202 * <p> This API will return false in scenarios like the profile on the 203 * Bluetooth device is not in connected state etc. When this API returns, 204 * true, it is guaranteed that the connection state change 205 * intent will be broadcasted with the state. Users can get the 206 * disconnection state of the profile from this intent. 207 * 208 * <p> If the disconnection is initiated by a remote device, the state 209 * will transition from {@link #STATE_CONNECTED} to 210 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 211 * host (local) device the state will transition from 212 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 213 * state {@link #STATE_DISCONNECTED}. The transition to 214 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 215 * two scenarios. 216 * 217 * @param device Remote Bluetooth Device 218 * @return false on immediate error, true otherwise 219 * @hide 220 */ 221 @RequiresBluetoothConnectPermission 222 @RequiresPermission(allOf = { 223 android.Manifest.permission.BLUETOOTH_CONNECT, 224 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 225 }) disconnect(BluetoothDevice device)226 public boolean disconnect(BluetoothDevice device) { 227 if (DBG) log("disconnect(" + device + ")"); 228 final IBluetoothHearingAid service = getService(); 229 try { 230 if (service != null && isEnabled() && isValidDevice(device)) { 231 return service.disconnect(device, mAttributionSource); 232 } 233 if (service == null) Log.w(TAG, "Proxy not attached to service"); 234 return false; 235 } catch (RemoteException e) { 236 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 237 return false; 238 } 239 } 240 241 /** 242 * {@inheritDoc} 243 */ 244 @Override 245 @RequiresBluetoothConnectPermission 246 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectedDevices()247 public @NonNull List<BluetoothDevice> getConnectedDevices() { 248 if (VDBG) log("getConnectedDevices()"); 249 final IBluetoothHearingAid service = getService(); 250 try { 251 if (service != null && isEnabled()) { 252 return Attributable.setAttributionSource( 253 service.getConnectedDevices(mAttributionSource), mAttributionSource); 254 } 255 if (service == null) Log.w(TAG, "Proxy not attached to service"); 256 return new ArrayList<BluetoothDevice>(); 257 } catch (RemoteException e) { 258 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 259 return new ArrayList<BluetoothDevice>(); 260 } 261 } 262 263 /** 264 * {@inheritDoc} 265 */ 266 @Override 267 @RequiresBluetoothConnectPermission 268 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getDevicesMatchingConnectionStates( @onNull int[] states)269 public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( 270 @NonNull int[] states) { 271 if (VDBG) log("getDevicesMatchingStates()"); 272 final IBluetoothHearingAid service = getService(); 273 try { 274 if (service != null && isEnabled()) { 275 return Attributable.setAttributionSource( 276 service.getDevicesMatchingConnectionStates(states, mAttributionSource), 277 mAttributionSource); 278 } 279 if (service == null) Log.w(TAG, "Proxy not attached to service"); 280 return new ArrayList<BluetoothDevice>(); 281 } catch (RemoteException e) { 282 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 283 return new ArrayList<BluetoothDevice>(); 284 } 285 } 286 287 /** 288 * {@inheritDoc} 289 */ 290 @Override 291 @RequiresBluetoothConnectPermission 292 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectionState( @onNull BluetoothDevice device)293 public @BluetoothProfile.BtProfileState int getConnectionState( 294 @NonNull BluetoothDevice device) { 295 if (VDBG) log("getState(" + device + ")"); 296 final IBluetoothHearingAid service = getService(); 297 try { 298 if (service != null && isEnabled() 299 && isValidDevice(device)) { 300 return service.getConnectionState(device, mAttributionSource); 301 } 302 if (service == null) Log.w(TAG, "Proxy not attached to service"); 303 return BluetoothProfile.STATE_DISCONNECTED; 304 } catch (RemoteException e) { 305 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 306 return BluetoothProfile.STATE_DISCONNECTED; 307 } 308 } 309 310 /** 311 * Select a connected device as active. 312 * 313 * The active device selection is per profile. An active device's 314 * purpose is profile-specific. For example, Hearing Aid audio 315 * streaming is to the active Hearing Aid device. If a remote device 316 * is not connected, it cannot be selected as active. 317 * 318 * <p> This API returns false in scenarios like the profile on the 319 * device is not connected or Bluetooth is not turned on. 320 * When this API returns true, it is guaranteed that the 321 * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted 322 * with the active device. 323 * 324 * @param device the remote Bluetooth device. Could be null to clear 325 * the active device and stop streaming audio to a Bluetooth device. 326 * @return false on immediate error, true otherwise 327 * @hide 328 */ 329 @RequiresLegacyBluetoothAdminPermission 330 @RequiresBluetoothConnectPermission 331 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 332 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) setActiveDevice(@ullable BluetoothDevice device)333 public boolean setActiveDevice(@Nullable BluetoothDevice device) { 334 if (DBG) log("setActiveDevice(" + device + ")"); 335 final IBluetoothHearingAid service = getService(); 336 try { 337 if (service != null && isEnabled() 338 && ((device == null) || isValidDevice(device))) { 339 service.setActiveDevice(device, mAttributionSource); 340 return true; 341 } 342 if (service == null) Log.w(TAG, "Proxy not attached to service"); 343 return false; 344 } catch (RemoteException e) { 345 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 346 return false; 347 } 348 } 349 350 /** 351 * Get the connected physical Hearing Aid devices that are active 352 * 353 * @return the list of active devices. The first element is the left active 354 * device; the second element is the right active device. If either or both side 355 * is not active, it will be null on that position. Returns empty list on error. 356 * @hide 357 */ 358 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 359 @RequiresLegacyBluetoothPermission 360 @RequiresBluetoothConnectPermission 361 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getActiveDevices()362 public @NonNull List<BluetoothDevice> getActiveDevices() { 363 if (VDBG) log("getActiveDevices()"); 364 final IBluetoothHearingAid service = getService(); 365 try { 366 if (service != null && isEnabled()) { 367 return Attributable.setAttributionSource( 368 service.getActiveDevices(mAttributionSource), mAttributionSource); 369 } 370 if (service == null) Log.w(TAG, "Proxy not attached to service"); 371 return new ArrayList<>(); 372 } catch (RemoteException e) { 373 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 374 return new ArrayList<>(); 375 } 376 } 377 378 /** 379 * Set priority of the profile 380 * 381 * <p> The device should already be paired. 382 * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, 383 * 384 * @param device Paired bluetooth device 385 * @param priority 386 * @return true if priority is set, false on error 387 * @hide 388 */ 389 @RequiresBluetoothConnectPermission 390 @RequiresPermission(allOf = { 391 android.Manifest.permission.BLUETOOTH_CONNECT, 392 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 393 }) setPriority(BluetoothDevice device, int priority)394 public boolean setPriority(BluetoothDevice device, int priority) { 395 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 396 return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); 397 } 398 399 /** 400 * Set connection policy of the profile 401 * 402 * <p> The device should already be paired. 403 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, 404 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 405 * 406 * @param device Paired bluetooth device 407 * @param connectionPolicy is the connection policy to set to for this profile 408 * @return true if connectionPolicy is set, false on error 409 * @hide 410 */ 411 @SystemApi 412 @RequiresBluetoothConnectPermission 413 @RequiresPermission(allOf = { 414 android.Manifest.permission.BLUETOOTH_CONNECT, 415 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 416 }) setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)417 public boolean setConnectionPolicy(@NonNull BluetoothDevice device, 418 @ConnectionPolicy int connectionPolicy) { 419 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 420 verifyDeviceNotNull(device, "setConnectionPolicy"); 421 final IBluetoothHearingAid service = getService(); 422 try { 423 if (service != null && isEnabled() 424 && isValidDevice(device)) { 425 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 426 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 427 return false; 428 } 429 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); 430 } 431 if (service == null) Log.w(TAG, "Proxy not attached to service"); 432 return false; 433 } catch (RemoteException e) { 434 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 435 return false; 436 } 437 } 438 439 /** 440 * Get the priority of the profile. 441 * 442 * <p> The priority can be any of: 443 * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 444 * 445 * @param device Bluetooth device 446 * @return priority of the device 447 * @hide 448 */ 449 @RequiresBluetoothConnectPermission 450 @RequiresPermission(allOf = { 451 android.Manifest.permission.BLUETOOTH_CONNECT, 452 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 453 }) getPriority(BluetoothDevice device)454 public int getPriority(BluetoothDevice device) { 455 if (VDBG) log("getPriority(" + device + ")"); 456 return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); 457 } 458 459 /** 460 * Get the connection policy of the profile. 461 * 462 * <p> The connection policy can be any of: 463 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, 464 * {@link #CONNECTION_POLICY_UNKNOWN} 465 * 466 * @param device Bluetooth device 467 * @return connection policy of the device 468 * @hide 469 */ 470 @SystemApi 471 @RequiresBluetoothConnectPermission 472 @RequiresPermission(allOf = { 473 android.Manifest.permission.BLUETOOTH_CONNECT, 474 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 475 }) getConnectionPolicy(@onNull BluetoothDevice device)476 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { 477 if (VDBG) log("getConnectionPolicy(" + device + ")"); 478 verifyDeviceNotNull(device, "getConnectionPolicy"); 479 final IBluetoothHearingAid service = getService(); 480 try { 481 if (service != null && isEnabled() 482 && isValidDevice(device)) { 483 return service.getConnectionPolicy(device, mAttributionSource); 484 } 485 if (service == null) Log.w(TAG, "Proxy not attached to service"); 486 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 487 } catch (RemoteException e) { 488 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 489 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 490 } 491 } 492 493 /** 494 * Helper for converting a state to a string. 495 * 496 * For debug use only - strings are not internationalized. 497 * 498 * @hide 499 */ stateToString(int state)500 public static String stateToString(int state) { 501 switch (state) { 502 case STATE_DISCONNECTED: 503 return "disconnected"; 504 case STATE_CONNECTING: 505 return "connecting"; 506 case STATE_CONNECTED: 507 return "connected"; 508 case STATE_DISCONNECTING: 509 return "disconnecting"; 510 default: 511 return "<unknown state " + state + ">"; 512 } 513 } 514 515 /** 516 * Tells remote device to set an absolute volume. 517 * 518 * @param volume Absolute volume to be set on remote 519 * @hide 520 */ 521 @RequiresBluetoothConnectPermission 522 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) setVolume(int volume)523 public void setVolume(int volume) { 524 if (DBG) Log.d(TAG, "setVolume(" + volume + ")"); 525 526 final IBluetoothHearingAid service = getService(); 527 try { 528 if (service == null) { 529 Log.w(TAG, "Proxy not attached to service"); 530 return; 531 } 532 533 if (!isEnabled()) return; 534 535 service.setVolume(volume, mAttributionSource); 536 } catch (RemoteException e) { 537 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 538 } 539 } 540 541 /** 542 * Get the HiSyncId (unique hearing aid device identifier) of the device. 543 * 544 * <a href=https://source.android.com/devices/bluetooth/asha#hisyncid>HiSyncId documentation 545 * can be found here</a> 546 * 547 * @param device Bluetooth device 548 * @return the HiSyncId of the device 549 * @hide 550 */ 551 @SystemApi 552 @RequiresBluetoothConnectPermission 553 @RequiresPermission(allOf = { 554 android.Manifest.permission.BLUETOOTH_CONNECT, 555 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 556 }) getHiSyncId(@onNull BluetoothDevice device)557 public long getHiSyncId(@NonNull BluetoothDevice device) { 558 if (VDBG) { 559 log("getHiSyncId(" + device + ")"); 560 } 561 verifyDeviceNotNull(device, "getConnectionPolicy"); 562 final IBluetoothHearingAid service = getService(); 563 try { 564 if (service == null) { 565 Log.w(TAG, "Proxy not attached to service"); 566 return HI_SYNC_ID_INVALID; 567 } 568 569 if (!isEnabled() || !isValidDevice(device)) return HI_SYNC_ID_INVALID; 570 571 return service.getHiSyncId(device, mAttributionSource); 572 } catch (RemoteException e) { 573 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 574 return HI_SYNC_ID_INVALID; 575 } 576 } 577 578 /** 579 * Get the side of the device. 580 * 581 * @param device Bluetooth device. 582 * @return SIDE_LEFT or SIDE_RIGHT 583 * @hide 584 */ 585 @RequiresLegacyBluetoothPermission 586 @RequiresBluetoothConnectPermission 587 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getDeviceSide(BluetoothDevice device)588 public int getDeviceSide(BluetoothDevice device) { 589 if (VDBG) { 590 log("getDeviceSide(" + device + ")"); 591 } 592 final IBluetoothHearingAid service = getService(); 593 try { 594 if (service != null && isEnabled() 595 && isValidDevice(device)) { 596 return service.getDeviceSide(device, mAttributionSource); 597 } 598 if (service == null) Log.w(TAG, "Proxy not attached to service"); 599 return SIDE_LEFT; 600 } catch (RemoteException e) { 601 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 602 return SIDE_LEFT; 603 } 604 } 605 606 /** 607 * Get the mode of the device. 608 * 609 * @param device Bluetooth device 610 * @return MODE_MONAURAL or MODE_BINAURAL 611 * @hide 612 */ 613 @RequiresLegacyBluetoothPermission 614 @RequiresBluetoothConnectPermission 615 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getDeviceMode(BluetoothDevice device)616 public int getDeviceMode(BluetoothDevice device) { 617 if (VDBG) { 618 log("getDeviceMode(" + device + ")"); 619 } 620 final IBluetoothHearingAid service = getService(); 621 try { 622 if (service != null && isEnabled() 623 && isValidDevice(device)) { 624 return service.getDeviceMode(device, mAttributionSource); 625 } 626 if (service == null) Log.w(TAG, "Proxy not attached to service"); 627 return MODE_MONAURAL; 628 } catch (RemoteException e) { 629 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 630 return MODE_MONAURAL; 631 } 632 } 633 isEnabled()634 private boolean isEnabled() { 635 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 636 return false; 637 } 638 verifyDeviceNotNull(BluetoothDevice device, String methodName)639 private void verifyDeviceNotNull(BluetoothDevice device, String methodName) { 640 if (device == null) { 641 Log.e(TAG, methodName + ": device param is null"); 642 throw new IllegalArgumentException("Device cannot be null"); 643 } 644 } 645 isValidDevice(BluetoothDevice device)646 private boolean isValidDevice(BluetoothDevice device) { 647 if (device == null) return false; 648 649 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 650 return false; 651 } 652 log(String msg)653 private static void log(String msg) { 654 Log.d(TAG, msg); 655 } 656 } 657