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