Home | History | Annotate | Download | only in bluetooth
      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      */
    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      * 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)
    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      */
    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