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.Nullable;
21 import android.annotation.RequiresPermission;
22 import android.annotation.SdkConstant;
23 import android.annotation.SdkConstant.SdkConstantType;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.ServiceConnection;
28 import android.os.Binder;
29 import android.os.IBinder;
30 import android.os.RemoteException;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.GuardedBy;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.concurrent.locks.ReentrantReadWriteLock;
38 
39 /**
40  * This class provides the public APIs to control the Bluetooth Hearing Aid
41  * profile.
42  *
43  * <p>BluetoothHearingAid is a proxy object for controlling the Bluetooth Hearing Aid
44  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
45  * the BluetoothHearingAid proxy object.
46  *
47  * <p> Each method is protected with its appropriate permission.
48  * @hide
49  */
50 public final class BluetoothHearingAid implements BluetoothProfile {
51     private static final String TAG = "BluetoothHearingAid";
52     private static final boolean DBG = false;
53     private static final boolean VDBG = false;
54 
55     /**
56      * Intent used to broadcast the change in connection state of the Hearing Aid
57      * profile.
58      *
59      * <p>This intent will have 3 extras:
60      * <ul>
61      * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
62      * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
63      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
64      * </ul>
65      *
66      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
67      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
68      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
69      *
70      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
71      * receive.
72      */
73     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
74     public static final String ACTION_CONNECTION_STATE_CHANGED =
75             "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
76 
77     /**
78      * Intent used to broadcast the change in the Playing state of the Hearing Aid
79      * profile.
80      *
81      * <p>This intent will have 3 extras:
82      * <ul>
83      * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
84      * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
85      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
86      * </ul>
87      *
88      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
89      * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
90      *
91      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
92      * receive.
93      */
94     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
95     public static final String ACTION_PLAYING_STATE_CHANGED =
96             "android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED";
97 
98     /**
99      * Intent used to broadcast the selection of a connected device as active.
100      *
101      * <p>This intent will have one extra:
102      * <ul>
103      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
104      * be null if no device is active. </li>
105      * </ul>
106      *
107      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
108      * receive.
109      */
110     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
111     public static final String ACTION_ACTIVE_DEVICE_CHANGED =
112             "android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED";
113 
114     /**
115      * Hearing Aid device is streaming music. This state can be one of
116      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
117      * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
118      */
119     public static final int STATE_PLAYING = 10;
120 
121     /**
122      * Hearing Aid device is NOT streaming music. This state can be one of
123      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
124      * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
125      */
126     public static final int STATE_NOT_PLAYING = 11;
127 
128     /** This device represents Left Hearing Aid. */
129     public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT;
130 
131     /** This device represents Right Hearing Aid. */
132     public static final int SIDE_RIGHT = IBluetoothHearingAid.SIDE_RIGHT;
133 
134     /** This device is Monaural. */
135     public static final int MODE_MONAURAL = IBluetoothHearingAid.MODE_MONAURAL;
136 
137     /** This device is Binaural (should receive only left or right audio). */
138     public static final int MODE_BINAURAL = IBluetoothHearingAid.MODE_BINAURAL;
139 
140     /** Can't read ClientID for this device */
141     public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID;
142 
143     private Context mContext;
144     private ServiceListener mServiceListener;
145     private final ReentrantReadWriteLock mServiceLock = new ReentrantReadWriteLock();
146     @GuardedBy("mServiceLock")
147     private IBluetoothHearingAid mService;
148     private BluetoothAdapter mAdapter;
149 
150     private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
151             new IBluetoothStateChangeCallback.Stub() {
152                 public void onBluetoothStateChange(boolean up) {
153                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
154                     if (!up) {
155                         if (VDBG) Log.d(TAG, "Unbinding service...");
156                         try {
157                             mServiceLock.writeLock().lock();
158                             mService = null;
159                             mContext.unbindService(mConnection);
160                         } catch (Exception re) {
161                             Log.e(TAG, "", re);
162                         } finally {
163                             mServiceLock.writeLock().unlock();
164                         }
165                     } else {
166                         try {
167                             mServiceLock.readLock().lock();
168                             if (mService == null) {
169                                 if (VDBG) Log.d(TAG, "Binding service...");
170                                 doBind();
171                             }
172                         } catch (Exception re) {
173                             Log.e(TAG, "", re);
174                         } finally {
175                             mServiceLock.readLock().unlock();
176                         }
177                     }
178                 }
179             };
180 
181     /**
182      * Create a BluetoothHearingAid proxy object for interacting with the local
183      * Bluetooth Hearing Aid service.
184      */
BluetoothHearingAid(Context context, ServiceListener l)185     /*package*/ BluetoothHearingAid(Context context, ServiceListener l) {
186         mContext = context;
187         mServiceListener = l;
188         mAdapter = BluetoothAdapter.getDefaultAdapter();
189         IBluetoothManager mgr = mAdapter.getBluetoothManager();
190         if (mgr != null) {
191             try {
192                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
193             } catch (RemoteException e) {
194                 Log.e(TAG, "", e);
195             }
196         }
197 
198         doBind();
199     }
200 
doBind()201     void doBind() {
202         Intent intent = new Intent(IBluetoothHearingAid.class.getName());
203         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
204         intent.setComponent(comp);
205         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
206                 android.os.Process.myUserHandle())) {
207             Log.e(TAG, "Could not bind to Bluetooth Hearing Aid Service with " + intent);
208             return;
209         }
210     }
211 
close()212     /*package*/ void close() {
213         mServiceListener = null;
214         IBluetoothManager mgr = mAdapter.getBluetoothManager();
215         if (mgr != null) {
216             try {
217                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
218             } catch (Exception e) {
219                 Log.e(TAG, "", e);
220             }
221         }
222 
223         try {
224             mServiceLock.writeLock().lock();
225             if (mService != null) {
226                 mService = null;
227                 mContext.unbindService(mConnection);
228             }
229         } catch (Exception re) {
230             Log.e(TAG, "", re);
231         } finally {
232             mServiceLock.writeLock().unlock();
233         }
234     }
235 
236     @Override
finalize()237     public void finalize() {
238         // The empty finalize needs to be kept or the
239         // cts signature tests would fail.
240     }
241 
242     /**
243      * Initiate connection to a profile of the remote bluetooth device.
244      *
245      * <p> This API returns false in scenarios like the profile on the
246      * device is already connected or Bluetooth is not turned on.
247      * When this API returns true, it is guaranteed that
248      * connection state intent for the profile will be broadcasted with
249      * the state. Users can get the connection state of the profile
250      * from this intent.
251      *
252      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
253      * permission.
254      *
255      * @param device Remote Bluetooth Device
256      * @return false on immediate error, true otherwise
257      * @hide
258      */
connect(BluetoothDevice device)259     public boolean connect(BluetoothDevice device) {
260         if (DBG) log("connect(" + device + ")");
261         try {
262             mServiceLock.readLock().lock();
263             if (mService != null && isEnabled() && isValidDevice(device)) {
264                 return mService.connect(device);
265             }
266             if (mService == null) Log.w(TAG, "Proxy not attached to service");
267             return false;
268         } catch (RemoteException e) {
269             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
270             return false;
271         } finally {
272             mServiceLock.readLock().unlock();
273         }
274     }
275 
276     /**
277      * Initiate disconnection from a profile
278      *
279      * <p> This API will return false in scenarios like the profile on the
280      * Bluetooth device is not in connected state etc. When this API returns,
281      * true, it is guaranteed that the connection state change
282      * intent will be broadcasted with the state. Users can get the
283      * disconnection state of the profile from this intent.
284      *
285      * <p> If the disconnection is initiated by a remote device, the state
286      * will transition from {@link #STATE_CONNECTED} to
287      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
288      * host (local) device the state will transition from
289      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
290      * state {@link #STATE_DISCONNECTED}. The transition to
291      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
292      * two scenarios.
293      *
294      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
295      * permission.
296      *
297      * @param device Remote Bluetooth Device
298      * @return false on immediate error, true otherwise
299      * @hide
300      */
disconnect(BluetoothDevice device)301     public boolean disconnect(BluetoothDevice device) {
302         if (DBG) log("disconnect(" + device + ")");
303         try {
304             mServiceLock.readLock().lock();
305             if (mService != null && isEnabled() && isValidDevice(device)) {
306                 return mService.disconnect(device);
307             }
308             if (mService == null) Log.w(TAG, "Proxy not attached to service");
309             return false;
310         } catch (RemoteException e) {
311             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
312             return false;
313         } finally {
314             mServiceLock.readLock().unlock();
315         }
316     }
317 
318     /**
319      * {@inheritDoc}
320      */
321     @Override
getConnectedDevices()322     public List<BluetoothDevice> getConnectedDevices() {
323         if (VDBG) log("getConnectedDevices()");
324         try {
325             mServiceLock.readLock().lock();
326             if (mService != null && isEnabled()) {
327                 return mService.getConnectedDevices();
328             }
329             if (mService == null) Log.w(TAG, "Proxy not attached to service");
330             return new ArrayList<BluetoothDevice>();
331         } catch (RemoteException e) {
332             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
333             return new ArrayList<BluetoothDevice>();
334         } finally {
335             mServiceLock.readLock().unlock();
336         }
337     }
338 
339     /**
340      * {@inheritDoc}
341      */
342     @Override
getDevicesMatchingConnectionStates(int[] states)343     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
344         if (VDBG) log("getDevicesMatchingStates()");
345         try {
346             mServiceLock.readLock().lock();
347             if (mService != null && isEnabled()) {
348                 return mService.getDevicesMatchingConnectionStates(states);
349             }
350             if (mService == null) Log.w(TAG, "Proxy not attached to service");
351             return new ArrayList<BluetoothDevice>();
352         } catch (RemoteException e) {
353             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
354             return new ArrayList<BluetoothDevice>();
355         } finally {
356             mServiceLock.readLock().unlock();
357         }
358     }
359 
360     /**
361      * {@inheritDoc}
362      */
363     @Override
getConnectionState(BluetoothDevice device)364     public int getConnectionState(BluetoothDevice device) {
365         if (VDBG) log("getState(" + device + ")");
366         try {
367             mServiceLock.readLock().lock();
368             if (mService != null && isEnabled()
369                     && isValidDevice(device)) {
370                 return mService.getConnectionState(device);
371             }
372             if (mService == null) Log.w(TAG, "Proxy not attached to service");
373             return BluetoothProfile.STATE_DISCONNECTED;
374         } catch (RemoteException e) {
375             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
376             return BluetoothProfile.STATE_DISCONNECTED;
377         } finally {
378             mServiceLock.readLock().unlock();
379         }
380     }
381 
382     /**
383      * Select a connected device as active.
384      *
385      * The active device selection is per profile. An active device's
386      * purpose is profile-specific. For example, Hearing Aid audio
387      * streaming is to the active Hearing Aid device. If a remote device
388      * is not connected, it cannot be selected as active.
389      *
390      * <p> This API returns false in scenarios like the profile on the
391      * device is not connected or Bluetooth is not turned on.
392      * When this API returns true, it is guaranteed that the
393      * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
394      * with the active device.
395      *
396      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
397      * permission.
398      *
399      * @param device the remote Bluetooth device. Could be null to clear
400      * the active device and stop streaming audio to a Bluetooth device.
401      * @return false on immediate error, true otherwise
402      * @hide
403      */
setActiveDevice(@ullable BluetoothDevice device)404     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
405         if (DBG) log("setActiveDevice(" + device + ")");
406         try {
407             mServiceLock.readLock().lock();
408             if (mService != null && isEnabled()
409                     && ((device == null) || isValidDevice(device))) {
410                 mService.setActiveDevice(device);
411                 return true;
412             }
413             if (mService == null) Log.w(TAG, "Proxy not attached to service");
414             return false;
415         } catch (RemoteException e) {
416             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
417             return false;
418         } finally {
419             mServiceLock.readLock().unlock();
420         }
421     }
422 
423     /**
424      * Get the connected physical Hearing Aid devices that are active
425      *
426      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
427      * permission.
428      *
429      * @return the list of active devices. The first element is the left active
430      * device; the second element is the right active device. If either or both side
431      * is not active, it will be null on that position. Returns empty list on error.
432      * @hide
433      */
434     @RequiresPermission(Manifest.permission.BLUETOOTH)
getActiveDevices()435     public List<BluetoothDevice> getActiveDevices() {
436         if (VDBG) log("getActiveDevices()");
437         try {
438             mServiceLock.readLock().lock();
439             if (mService != null && isEnabled()) {
440                 return mService.getActiveDevices();
441             }
442             if (mService == null) Log.w(TAG, "Proxy not attached to service");
443             return new ArrayList<>();
444         } catch (RemoteException e) {
445             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
446             return new ArrayList<>();
447         } finally {
448             mServiceLock.readLock().unlock();
449         }
450     }
451 
452     /**
453      * Set priority of the profile
454      *
455      * <p> The device should already be paired.
456      * Priority can be one of {@link #PRIORITY_ON} orgetBluetoothManager
457      * {@link #PRIORITY_OFF},
458      *
459      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
460      * permission.
461      *
462      * @param device Paired bluetooth device
463      * @param priority
464      * @return true if priority is set, false on error
465      * @hide
466      */
setPriority(BluetoothDevice device, int priority)467     public boolean setPriority(BluetoothDevice device, int priority) {
468         if (DBG) log("setPriority(" + device + ", " + priority + ")");
469         try {
470             mServiceLock.readLock().lock();
471             if (mService != null && isEnabled()
472                     && isValidDevice(device)) {
473                 if (priority != BluetoothProfile.PRIORITY_OFF
474                         && priority != BluetoothProfile.PRIORITY_ON) {
475                     return false;
476                 }
477                 return mService.setPriority(device, priority);
478             }
479             if (mService == null) Log.w(TAG, "Proxy not attached to service");
480             return false;
481         } catch (RemoteException e) {
482             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
483             return false;
484         } finally {
485             mServiceLock.readLock().unlock();
486         }
487     }
488 
489     /**
490      * Get the priority of the profile.
491      *
492      * <p> The priority can be any of:
493      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
494      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
495      *
496      * @param device Bluetooth device
497      * @return priority of the device
498      * @hide
499      */
500     @RequiresPermission(Manifest.permission.BLUETOOTH)
getPriority(BluetoothDevice device)501     public int getPriority(BluetoothDevice device) {
502         if (VDBG) log("getPriority(" + device + ")");
503         try {
504             mServiceLock.readLock().lock();
505             if (mService != null && isEnabled()
506                     && isValidDevice(device)) {
507                 return mService.getPriority(device);
508             }
509             if (mService == null) Log.w(TAG, "Proxy not attached to service");
510             return BluetoothProfile.PRIORITY_OFF;
511         } catch (RemoteException e) {
512             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
513             return BluetoothProfile.PRIORITY_OFF;
514         } finally {
515             mServiceLock.readLock().unlock();
516         }
517     }
518 
519     /**
520      * Helper for converting a state to a string.
521      *
522      * For debug use only - strings are not internationalized.
523      *
524      * @hide
525      */
stateToString(int state)526     public static String stateToString(int state) {
527         switch (state) {
528             case STATE_DISCONNECTED:
529                 return "disconnected";
530             case STATE_CONNECTING:
531                 return "connecting";
532             case STATE_CONNECTED:
533                 return "connected";
534             case STATE_DISCONNECTING:
535                 return "disconnecting";
536             case STATE_PLAYING:
537                 return "playing";
538             case STATE_NOT_PLAYING:
539                 return "not playing";
540             default:
541                 return "<unknown state " + state + ">";
542         }
543     }
544 
545     /**
546      * Get the volume of the device.
547      *
548      * <p> The volume is between -128 dB (mute) to 0 dB.
549      *
550      * @return volume of the hearing aid device.
551      * @hide
552      */
553     @RequiresPermission(Manifest.permission.BLUETOOTH)
getVolume()554     public int getVolume() {
555         if (VDBG) {
556             log("getVolume()");
557         }
558         try {
559             mServiceLock.readLock().lock();
560             if (mService != null && isEnabled()) {
561                 return mService.getVolume();
562             }
563             if (mService == null) Log.w(TAG, "Proxy not attached to service");
564             return 0;
565         } catch (RemoteException e) {
566             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
567             return 0;
568         } finally {
569             mServiceLock.readLock().unlock();
570         }
571     }
572 
573     /**
574      * Tells remote device to adjust volume. Uses the following values:
575      * <ul>
576      * <li>{@link AudioManager#ADJUST_LOWER}</li>
577      * <li>{@link AudioManager#ADJUST_RAISE}</li>
578      * <li>{@link AudioManager#ADJUST_MUTE}</li>
579      * <li>{@link AudioManager#ADJUST_UNMUTE}</li>
580      * </ul>
581      *
582      * @param direction One of the supported adjust values.
583      * @hide
584      */
585     @RequiresPermission(Manifest.permission.BLUETOOTH)
adjustVolume(int direction)586     public void adjustVolume(int direction) {
587         if (DBG) log("adjustVolume(" + direction + ")");
588 
589         try {
590             mServiceLock.readLock().lock();
591 
592             if (mService == null) {
593                 Log.w(TAG, "Proxy not attached to service");
594                 return;
595             }
596 
597             if (!isEnabled()) return;
598 
599             mService.adjustVolume(direction);
600         } catch (RemoteException e) {
601             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
602         } finally {
603             mServiceLock.readLock().unlock();
604         }
605     }
606 
607     /**
608      * Tells remote device to set an absolute volume.
609      *
610      * @param volume Absolute volume to be set on remote
611      * @hide
612      */
setVolume(int volume)613     public void setVolume(int volume) {
614         if (DBG) Log.d(TAG, "setVolume(" + volume + ")");
615 
616         try {
617             mServiceLock.readLock().lock();
618             if (mService == null) {
619                 Log.w(TAG, "Proxy not attached to service");
620                 return;
621             }
622 
623             if (!isEnabled()) return;
624 
625             mService.setVolume(volume);
626         } catch (RemoteException e) {
627             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
628         } finally {
629             mServiceLock.readLock().unlock();
630         }
631     }
632 
633     /**
634      * Get the CustomerId of the device.
635      *
636      * @param device Bluetooth device
637      * @return the CustomerId of the device
638      * @hide
639      */
640     @RequiresPermission(Manifest.permission.BLUETOOTH)
getHiSyncId(BluetoothDevice device)641     public long getHiSyncId(BluetoothDevice device) {
642         if (VDBG) {
643             log("getCustomerId(" + device + ")");
644         }
645         try {
646             mServiceLock.readLock().lock();
647             if (mService == null) {
648                 Log.w(TAG, "Proxy not attached to service");
649                 return HI_SYNC_ID_INVALID;
650             }
651 
652             if (!isEnabled() || !isValidDevice(device)) return HI_SYNC_ID_INVALID;
653 
654             return mService.getHiSyncId(device);
655         } catch (RemoteException e) {
656             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
657             return HI_SYNC_ID_INVALID;
658         } finally {
659             mServiceLock.readLock().unlock();
660         }
661     }
662 
663     /**
664      * Get the side of the device.
665      *
666      * @param device Bluetooth device.
667      * @return SIDE_LEFT or SIDE_RIGHT
668      * @hide
669      */
670     @RequiresPermission(Manifest.permission.BLUETOOTH)
getDeviceSide(BluetoothDevice device)671     public int getDeviceSide(BluetoothDevice device) {
672         if (VDBG) {
673             log("getDeviceSide(" + device + ")");
674         }
675         try {
676             mServiceLock.readLock().lock();
677             if (mService != null && isEnabled()
678                     && isValidDevice(device)) {
679                 return mService.getDeviceSide(device);
680             }
681             if (mService == null) Log.w(TAG, "Proxy not attached to service");
682             return SIDE_LEFT;
683         } catch (RemoteException e) {
684             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
685             return SIDE_LEFT;
686         } finally {
687             mServiceLock.readLock().unlock();
688         }
689     }
690 
691     /**
692      * Get the mode of the device.
693      *
694      * @param device Bluetooth device
695      * @return MODE_MONAURAL or MODE_BINAURAL
696      * @hide
697      */
698     @RequiresPermission(Manifest.permission.BLUETOOTH)
getDeviceMode(BluetoothDevice device)699     public int getDeviceMode(BluetoothDevice device) {
700         if (VDBG) {
701             log("getDeviceMode(" + device + ")");
702         }
703         try {
704             mServiceLock.readLock().lock();
705             if (mService != null && isEnabled()
706                     && isValidDevice(device)) {
707                 return mService.getDeviceMode(device);
708             }
709             if (mService == null) Log.w(TAG, "Proxy not attached to service");
710             return MODE_MONAURAL;
711         } catch (RemoteException e) {
712             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
713             return MODE_MONAURAL;
714         } finally {
715             mServiceLock.readLock().unlock();
716         }
717     }
718 
719     private final ServiceConnection mConnection = new ServiceConnection() {
720         public void onServiceConnected(ComponentName className, IBinder service) {
721             if (DBG) Log.d(TAG, "Proxy object connected");
722             try {
723                 mServiceLock.writeLock().lock();
724                 mService = IBluetoothHearingAid.Stub.asInterface(Binder.allowBlocking(service));
725             } finally {
726                 mServiceLock.writeLock().unlock();
727             }
728 
729             if (mServiceListener != null) {
730                 mServiceListener.onServiceConnected(BluetoothProfile.HEARING_AID,
731                                                     BluetoothHearingAid.this);
732             }
733         }
734 
735         public void onServiceDisconnected(ComponentName className) {
736             if (DBG) Log.d(TAG, "Proxy object disconnected");
737             try {
738                 mServiceLock.writeLock().lock();
739                 mService = null;
740             } finally {
741                 mServiceLock.writeLock().unlock();
742             }
743             if (mServiceListener != null) {
744                 mServiceListener.onServiceDisconnected(BluetoothProfile.HEARING_AID);
745             }
746         }
747     };
748 
isEnabled()749     private boolean isEnabled() {
750         if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
751         return false;
752     }
753 
isValidDevice(BluetoothDevice device)754     private boolean isValidDevice(BluetoothDevice device) {
755         if (device == null) return false;
756 
757         if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
758         return false;
759     }
760 
log(String msg)761     private static void log(String msg) {
762         Log.d(TAG, msg);
763     }
764 }
765