BluetoothHearingAid.java revision d7b359165968199e034bd46ca97526dbf719602d
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
17package android.bluetooth;
18
19import android.Manifest;
20import android.annotation.Nullable;
21import android.annotation.RequiresPermission;
22import android.annotation.SdkConstant;
23import android.annotation.SdkConstant.SdkConstantType;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.ServiceConnection;
28import android.os.Binder;
29import android.os.IBinder;
30import android.os.RemoteException;
31import android.util.Log;
32
33import com.android.internal.annotations.GuardedBy;
34
35import java.util.ArrayList;
36import java.util.List;
37import 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 */
50public 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     */
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
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
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
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     */
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     */
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
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
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
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     */
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     * Check whether the device is active.
425     *
426     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
427     * permission.
428     *
429     * @return the connected device that is active or null if no device
430     * is active
431     * @hide
432     */
433    @RequiresPermission(Manifest.permission.BLUETOOTH)
434    public boolean isActiveDevice(@Nullable BluetoothDevice device) {
435        if (VDBG) log("isActiveDevice()");
436        try {
437            mServiceLock.readLock().lock();
438            if (mService != null && isEnabled()
439                    && ((device == null) || isValidDevice(device))) {
440                return mService.isActiveDevice(device);
441            }
442            if (mService == null) Log.w(TAG, "Proxy not attached to service");
443            return false;
444        } catch (RemoteException e) {
445            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
446            return false;
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     */
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)
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     */
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)
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)
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     */
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)
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)
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)
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
749    private boolean isEnabled() {
750        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
751        return false;
752    }
753
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
761    private static void log(String msg) {
762        Log.d(TAG, msg);
763    }
764}
765